├── .github └── workflows │ └── runUnitTests.yml ├── .gitignore ├── .vscode ├── commandbar.json ├── launch.json ├── settings.json └── tasks.json ├── LICENSE ├── README.md ├── haxelib.json ├── langParser.hxml ├── res ├── achievements.cdb ├── cdbTest.cdb └── lang │ ├── legacy_fr.mo │ ├── legacy_fr.po │ ├── legacy_sourceTexts.pot │ ├── new_fr.po │ └── new_sourceTexts.pot ├── src ├── assets │ └── deepnightControllerIcons.aseprite └── dn │ ├── AbstractEnumFlags.hx │ ├── Args.hx │ ├── CdbHelper.hx │ ├── Changelog.hx │ ├── Chrono.hx │ ├── CiAssert.hx │ ├── Cinematic.hx │ ├── Col.hx │ ├── Cooldown.hx │ ├── DecisionHelper.hx │ ├── Delayer.hx │ ├── FilePath.hx │ ├── FileTools.hx │ ├── Gc.hx │ ├── Identify.hx │ ├── ImageDecoder.hx │ ├── Lib.hx │ ├── Log.hx │ ├── M.hx │ ├── MacroTools.hx │ ├── MarkerMap.hx │ ├── Process.hx │ ├── Rand.hx │ ├── RandomTools.hx │ ├── TinyTween.hx │ ├── Tweenie.hx │ ├── Version.hx │ ├── achievements │ ├── AbstractAchievementPlatform.hx │ ├── AchievementsManager.hx │ ├── LocalAchivementPlatform.hx │ └── SteamAchivementPlatform.hx │ ├── data │ ├── AchievementDb.hx │ ├── GetText.hx │ ├── JsonPretty.hx │ └── LocalStorage.hx │ ├── debug │ └── MemTrack.hx │ ├── geom │ ├── Bresenham.hx │ ├── Geom.hx │ └── GridPoint.hx │ ├── heaps │ ├── Boundless.hx │ ├── Chart.hx │ ├── FlowBg.hx │ ├── GameFocusHelper.hx │ ├── HParticle.hx │ ├── Palette.hx │ ├── PixelGrid.hx │ ├── Scaler.hx │ ├── ScreenWash.hx │ ├── Sfx.hx │ ├── Skewable.hx │ ├── StatsBox.hx │ ├── TiledTexture.hx │ ├── assets │ │ ├── Aseprite.hx │ │ ├── Atlas.hx │ │ ├── PixelLookup.hx │ │ ├── SfxDirectory.hx │ │ └── TexturePacker.hx │ ├── filter │ │ ├── Crt.hx │ │ ├── CustomTransformation.hx │ │ ├── Debug.hx │ │ ├── Edge.hx │ │ ├── FogTeint.hx │ │ ├── GradientDarkness.hx │ │ ├── GradientMap.hx │ │ ├── Invert.hx │ │ ├── Monochrome.hx │ │ ├── MotionBlur.hx │ │ ├── OverlayTexture.hx │ │ ├── PixelOutline.hx │ │ └── RadialColor.hx │ ├── input │ │ ├── Controller.hx │ │ ├── ControllerAccess.hx │ │ ├── ControllerDebug.hx │ │ └── ControllerQueue.hx │ └── slib │ │ ├── AnimManager.hx │ │ ├── HSprite.hx │ │ ├── HSpriteBE.hx │ │ ├── HSpriteBatch.hx │ │ ├── SpriteInterface.hx │ │ ├── SpriteLib.hx │ │ └── SpritePivot.hx │ ├── js │ ├── ElectronDialogs.hx │ ├── ElectronTools.hx │ ├── ElectronUpdater.hx │ └── NodeTools.hx │ ├── legacy │ ├── Color.hx │ ├── ControlQueue.hx │ ├── Controller.hx │ ├── FlashColor.hx │ ├── GamePad.hx │ ├── GetText.hx │ ├── HaxeJson.hx │ ├── Lib.hx │ └── SavedData.hx │ ├── pathfinder │ └── AStar.hx │ ├── phys │ ├── Rope.hx │ └── Velocity.hx │ ├── store │ ├── StoreAPI.hx │ └── impl │ │ ├── DummyImpl.hx │ │ └── SteamImpl.hx │ └── struct │ ├── FixedArray.hx │ ├── Grid.hx │ ├── RandDeck.hx │ ├── RandList.hx │ ├── RecyclablePool.hx │ └── Stat.hx ├── steam.tests.hl.hxml ├── tests.base.hxml ├── tests.hl.hxml ├── tests.js.hxml └── tests ├── CdbTest.hx ├── LangParser.hx └── Tests.hx /.github/workflows/runUnitTests.yml: -------------------------------------------------------------------------------- 1 | name: Unit tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build: 13 | 14 | strategy: 15 | matrix: 16 | os: [windows-latest] 17 | haxe: [4.3.3] 18 | fail-fast: true 19 | runs-on: windows-latest 20 | 21 | steps: 22 | # Checkout & install haxe 23 | - uses: actions/checkout@v2 24 | - uses: krdlab/setup-haxe@v1 25 | with: 26 | haxe-version: ${{ matrix.haxe }} 27 | - run: haxe -version 28 | 29 | # Install libs 30 | - run: haxelib git castle https://github.com/deepnight/castle 31 | - run: haxelib git hxnodejs https://github.com/HaxeFoundation/hxnodejs.git 32 | - run: haxelib list 33 | 34 | # Run tests 35 | - run: haxe tests.js.hxml -D verbose 36 | 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | test/test.hl 2 | tests/bin 3 | dump -------------------------------------------------------------------------------- /.vscode/commandbar.json: -------------------------------------------------------------------------------- 1 | { 2 | "skipTerminateQuickPick": true, 3 | "skipSwitchToOutput": false, 4 | "skipErrorMessage": true, 5 | "commands": [ 6 | { 7 | "text": "✪ JS", 8 | "color": "yellow", 9 | "command": "haxe tests.js.hxml", 10 | "alignment": "right", 11 | "skipTerminateQuickPick": false, 12 | "priority": -1 13 | }, 14 | { 15 | "text": "✪ HL", 16 | "color": "orange", 17 | "command": "haxe tests.hl.hxml", 18 | "alignment": "right", 19 | "skipTerminateQuickPick": false, 20 | "priority": -2 21 | }, 22 | { 23 | "text": "✪ HL verbose", 24 | "color": "orange", 25 | "command": "haxe tests.hl.hxml -D verbose", 26 | "alignment": "right", 27 | "skipTerminateQuickPick": false, 28 | "priority": -3 29 | }, 30 | { 31 | "text": "🌍 Lang", 32 | "color": "white", 33 | "command": "haxe langParser.hxml", 34 | "alignment": "right", 35 | "skipTerminateQuickPick": false, 36 | "priority": -4 37 | }, 38 | { 39 | "text": "➠ Submit to haxelib", 40 | "color": "white", 41 | "command": "start haxelib submit .", 42 | "alignment": "right", 43 | "skipTerminateQuickPick": false, 44 | "priority": -99 45 | } 46 | ] 47 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | ] 8 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | "titleBar.activeBackground": "#645c87", 4 | "titleBar.activeForeground": "#ffffff" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "Tests neko", 8 | "type": "hxml", 9 | "file": "tests.neko.hxml", 10 | "presentation": { 11 | "reveal": "always", 12 | "panel": "dedicated", 13 | "clear": true 14 | }, 15 | "group": "build" 16 | }, 17 | { 18 | "type": "haxe", 19 | "args": "active configuration", 20 | "presentation": { 21 | "reveal": "never", 22 | "panel": "dedicated", 23 | "clear": true 24 | }, 25 | "group": { 26 | "kind": "build", 27 | "isDefault": true 28 | }, 29 | "label": "haxe: active configuration" 30 | } 31 | ] 32 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Sébastien Bénard 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | The general purpose libs I use in all my Haxe projects. If you want to build one of [my project](https://github.com/deepnight/), you will need them. 4 | 5 | [![Unit tests](https://github.com/deepnight/deepnightLibs/actions/workflows/runUnitTests.yml/badge.svg)](https://github.com/deepnight/deepnightLibs/actions/workflows/runUnitTests.yml) 6 | 7 | # Install 8 | 9 | ## Stable 10 | 11 | Use this version if you plan to use the libs for your own projects. 12 | 13 | ```bash 14 | haxelib install deepnightLibs 15 | ``` 16 | 17 | ## Latest Git version (mostly stable) 18 | 19 | This is the version I use and update very frequently. 20 | 21 | Pick this version if you're building one of my GitHub projects, such as LDtk or one of my gamejam entry. 22 | 23 | ```bash 24 | haxelib git deepnightLibs https://github.com/deepnight/deepnightLibs 25 | ``` 26 | 27 | # Usage 28 | 29 | In your HXML file, add: 30 | ```hxml 31 | -lib deepnightLibs 32 | ``` 33 | 34 | All the libs are in the `dn.*` package. 35 | 36 | ```haxe 37 | class MyProject { 38 | public function new() { 39 | trace( dn.M.fmin(0.3,0.8) ); // 0.3 40 | } 41 | } 42 | ``` 43 | 44 | # Tips 45 | 46 | Use global imports! To import libs in every HX files, just add a ``import.hx`` (this exact name & caps) file to the **root** of your ``src`` folder: 47 | 48 | ``` 49 | dn.*; 50 | dn.Col as C; 51 | ``` 52 | 53 | - The first line imports all classes in ``dn`` package, 54 | 55 | - The second one imports ``dn.Col`` as an alias "C", 56 | 57 | - Feel free to add your own convenient imports there. 58 | 59 | # Noteworthy classes 60 | 61 | ## dn.M 62 | 63 | My re-implementation of the Math class, with high-performances in mind: 64 | 65 | ```haxe 66 | M.fmin(0.5, 0.9); // 0.5 67 | M.frandRange(0, 2); // random Float number between 0 -> 2 68 | M.randRange(0, 2); // either 0, 1 or 2 69 | M.pow(val, 2); // turns into val*val at compilation time 70 | ``` 71 | 72 | ## dn.Col 73 | 74 | The color management lib, with performance in mind. 75 | 76 | A color is an abstract class that revolves around a single Integer value (the color in 0xaarrggbb format). Most methods are just operations on this internal Int value. 77 | 78 | ```haxe 79 | import dn.Col; 80 | var c : Col = 0xff0000; // red 81 | var c : Col = Red; // also red, but using and enum constant 82 | var c : Col = "#ff0000"; // still red, but the conversion from a constant String to Int is hard-inlined using macros. Costs 0 at runtime. 83 | var darker = c.toBlack(0.5); // return a 50% darker red 84 | var c2 = c.to(Yellow, 0.3); // return a mix from current color with 30% of the standard yellow 85 | ``` 86 | 87 | ## dn.DecisionHelper 88 | 89 | A nice tool to easily pick a value among many others using any custom criterion. 90 | 91 | ```haxe 92 | var arr = [ "a", "foo", "bar", "food", "hello" ]; 93 | 94 | var dh = new dn.DecisionHelper(arr); 95 | 96 | /* Iterates all values in arr and increase their internal score by 1 if they contain the letter "o". */ 97 | dh.score( v -> StringTools.contains(v,"o") ? 1 : 0 ); 98 | 99 | /* Increase score of each values using 10% of their length (ie. longer strings get slightly higher score) */ 100 | dh.score( v -> v.length*0.1 ); 101 | 102 | /* Discard any value containing the letter "h" */ 103 | dh.remove( v -> StringTools.contains(v,"h") ); 104 | 105 | /* Only keep values with length>1 */ 106 | dh.keepOnly( v -> v.length>1 ); 107 | 108 | trace( dh.getBest() ); // -> food 109 | /* Internal scores: a (discarded), foo (1.3), bar (0.3), food (1.4), hello (discarded). */ 110 | ``` 111 | -------------------------------------------------------------------------------- /haxelib.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "deepnightLibs", 3 | "url" : "https://github.com/deepnight/deepnightLibs", 4 | "license": "Apache", 5 | "tags": ["Heaps","Hashlink","package","deepnight"], 6 | "description": "The general personal libs I use in my Haxe projects.", 7 | "version": "1.0.85", 8 | "classPath": "src/", 9 | "releasenote": "Misc stuff, bug fixes (check git log)", 10 | "contributors": ["sbenard"], 11 | "dependencies": {} 12 | } -------------------------------------------------------------------------------- /langParser.hxml: -------------------------------------------------------------------------------- 1 | -cp src 2 | -cp tests 3 | -main LangParser 4 | -lib castle 5 | -D potools 6 | -hl tests/bin/langParser.hl 7 | 8 | --next 9 | -cmd hl tests/bin/langParser.hl -------------------------------------------------------------------------------- /res/achievements.cdb: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "name": "achievements", 5 | "columns": [ 6 | { 7 | "typeStr": "0", 8 | "name": "Id" 9 | }, 10 | { 11 | "typeStr": "1", 12 | "name": "Name", 13 | "display": null 14 | }, 15 | { 16 | "typeStr": "1", 17 | "name": "Description", 18 | "display": null 19 | }, 20 | { 21 | "typeStr": "7", 22 | "name": "Picture", 23 | "opt": true, 24 | "display": null 25 | }, 26 | { 27 | "typeStr": "1", 28 | "name": "steamID", 29 | "opt": true 30 | }, 31 | { 32 | "typeStr": "1", 33 | "name": "xboxID", 34 | "opt": true 35 | }, 36 | { 37 | "typeStr": "1", 38 | "name": "psID", 39 | "display": null 40 | } 41 | ], 42 | "lines": [ 43 | { 44 | "Name": "test", 45 | "psID": "qsd", 46 | "steamID": "sd", 47 | "xboxID": "sd", 48 | "Id": "test" 49 | } 50 | ], 51 | "separators": [], 52 | "props": {} 53 | } 54 | ], 55 | "customTypes": [], 56 | "compress": false 57 | } -------------------------------------------------------------------------------- /res/cdbTest.cdb: -------------------------------------------------------------------------------- 1 | { 2 | "sheets": [ 3 | { 4 | "name": "texts", 5 | "columns": [ 6 | { 7 | "typeStr": "0", 8 | "name": "id" 9 | }, 10 | { 11 | "typeStr": "1", 12 | "name": "text", 13 | "kind": "localizable" 14 | } 15 | ], 16 | "lines": [ 17 | { 18 | "id": "test_1", 19 | "text": "I'm a CDB text" 20 | }, 21 | { 22 | "id": "test_2", 23 | "text": "I'm a CDB text with commentary||!Commentary" 24 | }, 25 | { 26 | "id": "test_3", 27 | "text": "I'm a CDB text with param: ::param::" 28 | }, 29 | { 30 | "id": "test_4", 31 | "text": "I'm a CDB text with param: ::param:: and commentary||!Commentary" 32 | } 33 | ], 34 | "separators": [], 35 | "props": {} 36 | } 37 | ], 38 | "customTypes": [], 39 | "compress": false 40 | } -------------------------------------------------------------------------------- /res/lang/legacy_fr.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deepnight/deepnightLibs/2cbad91a45bad941dc08df74f0fe369ced23ca96/res/lang/legacy_fr.mo -------------------------------------------------------------------------------- /res/lang/legacy_fr.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: \n" 4 | "POT-Creation-Date: \n" 5 | "PO-Revision-Date: 2021-07-08 13:38+0200\n" 6 | "Last-Translator: \n" 7 | "Language-Team: \n" 8 | "Language: fr\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "X-Generator: Poedit 3.0\n" 13 | "X-Poedit-Basepath: .\n" 14 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 15 | 16 | #: src/dn/legacy/GetText.hx:660 17 | msgid "Normal text" 18 | msgstr "Texte normal" 19 | 20 | #. !I'm the commentary 21 | #: src/dn/legacy/GetText.hx:661 22 | msgid "Text with commentary" 23 | msgstr "Texte avec commentaire" 24 | 25 | #: src/dn/legacy/GetText.hx:662 26 | msgid "Text with parameters: ::param::" 27 | msgstr "Texte avec paramètres: ::param::" 28 | 29 | #. !Commentary 30 | #: src/dn/legacy/GetText.hx:663 31 | msgid "Text with parameters: ::param:: and commentary" 32 | msgstr "Texte avec paramètres: ::param:: et commentaire" 33 | 34 | #: res/cdbTest.cdb:texts/#0.text 35 | msgid "I'm a CDB text" 36 | msgstr "Je suis un texte CDB" 37 | 38 | #. !Commentary 39 | #: res/cdbTest.cdb:texts/#1.text 40 | msgid "I'm a CDB text with commentary" 41 | msgstr "Je suis un texte CDB avec commentaire" 42 | 43 | #: res/cdbTest.cdb:texts/#2.text 44 | msgid "I'm a CDB text with param: ::param::" 45 | msgstr "Je suis un texte CDB avec paramètre: ::param::" 46 | 47 | #. !Commentary 48 | #: res/cdbTest.cdb:texts/#3.text 49 | msgid "I'm a CDB text with param: ::param:: and commentary" 50 | msgstr "Je suis un texte CDB avec paramètre: ::param:: et commentaire" 51 | -------------------------------------------------------------------------------- /res/lang/legacy_sourceTexts.pot: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Content-Type: text/plain; charset=UTF-8\n" 4 | "Content-Transfer-Encoding: 8bit\n" 5 | "MIME-Version: 1.0\n" 6 | 7 | #: src/dn/data/GetText.hx:927 src/dn/legacy/GetText.hx:660 8 | msgid "Normal text" 9 | msgstr "" 10 | 11 | #: src/dn/data/GetText.hx:928 src/dn/legacy/GetText.hx:662 12 | msgid "Text with parameters: ::param::" 13 | msgstr "" 14 | 15 | #. @Context 1 16 | #: src/dn/data/GetText.hx:937 17 | msgid "Ambiguous" 18 | msgstr "" 19 | 20 | #. @Context 2 21 | #: src/dn/data/GetText.hx:938 22 | msgid "Ambiguous" 23 | msgstr "" 24 | 25 | #. !I'm the commentary 26 | #: src/dn/legacy/GetText.hx:661 27 | msgid "Text with commentary" 28 | msgstr "" 29 | 30 | #. !Commentary 31 | #: src/dn/legacy/GetText.hx:663 32 | msgid "Text with parameters: ::param:: and commentary" 33 | msgstr "" 34 | 35 | #: res/cdbTest.cdb:texts/#0.text 36 | msgid "I'm a CDB text" 37 | msgstr "" 38 | 39 | #. !Commentary 40 | #: res/cdbTest.cdb:texts/#1.text 41 | msgid "I'm a CDB text with commentary" 42 | msgstr "" 43 | 44 | #: res/cdbTest.cdb:texts/#2.text 45 | msgid "I'm a CDB text with param: ::param::" 46 | msgstr "" 47 | 48 | #. !Commentary 49 | #: res/cdbTest.cdb:texts/#3.text 50 | msgid "I'm a CDB text with param: ::param:: and commentary" 51 | msgstr "" 52 | 53 | 54 | -------------------------------------------------------------------------------- /res/lang/new_fr.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: \n" 4 | "POT-Creation-Date: \n" 5 | "PO-Revision-Date: \n" 6 | "Last-Translator: \n" 7 | "Language-Team: \n" 8 | "Language: fr\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "\"MIME-Version: 1.0\n" 13 | "X-Generator: Poedit 3.0\n" 14 | "Plural-Forms: nplurals=2; plural=(n > 1);\n" 15 | 16 | #: "src/dn/data/GetText.hx" "src/dn/legacy/GetText.hx" 17 | msgid "Normal text" 18 | msgstr "Texte normal" 19 | 20 | #: "src/dn/data/GetText.hx" "src/dn/legacy/GetText.hx" 21 | msgid "Text with parameters: ::param::" 22 | msgstr "Texte avec paramètres: ::param::" 23 | 24 | #: "src/dn/data/GetText.hx" 25 | msgctxt "Context 1" 26 | msgid "Ambiguous" 27 | msgstr "Ambigu dans contexte 1" 28 | 29 | #: "src/dn/data/GetText.hx" 30 | msgctxt "Context 2" 31 | msgid "Ambiguous" 32 | msgstr "Ambigu dans contexte 2" 33 | 34 | #. I'm the commentary 35 | #: "src/dn/legacy/GetText.hx" 36 | msgid "Text with commentary" 37 | msgstr "Texte avec commentaire" 38 | 39 | #. Commentary 40 | #: "src/dn/legacy/GetText.hx" 41 | msgid "Text with parameters: ::param:: and commentary" 42 | msgstr "Texte avec paramètres: ::param:: et commentaire" 43 | 44 | # CastleDB 45 | #: "res/cdbTest.cdb:texts/#0.text" 46 | msgid "I'm a CDB text" 47 | msgstr "Je suis un texte CDB" 48 | 49 | # CastleDB 50 | #. Commentary 51 | #: "res/cdbTest.cdb:texts/#1.text" 52 | msgid "I'm a CDB text with commentary" 53 | msgstr "Je suis un texte CDB avec commentaire" 54 | 55 | # CastleDB 56 | #: "res/cdbTest.cdb:texts/#2.text" 57 | msgid "I'm a CDB text with param: ::param::" 58 | msgstr "Je suis un texte CDB avec paramètre : ::param::" 59 | 60 | # CastleDB 61 | #. Commentary 62 | #: "res/cdbTest.cdb:texts/#3.text" 63 | msgid "I'm a CDB text with param: ::param:: and commentary" 64 | msgstr "Je suis un texte CDB avec paramètre : ::param:: et commentaire" 65 | -------------------------------------------------------------------------------- /res/lang/new_sourceTexts.pot: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Content-Type: text/plain; charset=UTF-8\n" 4 | "Content-Transfer-Encoding: 8bit\n" 5 | "MIME-Version: 1.0\n" 6 | 7 | 8 | #: "src/dn/data/GetText.hx" 9 | #: "src/dn/legacy/GetText.hx" 10 | msgid "Normal text" 11 | msgstr "" 12 | 13 | #: "src/dn/data/GetText.hx" 14 | #: "src/dn/legacy/GetText.hx" 15 | msgid "Text with parameters: ::param::" 16 | msgstr "" 17 | 18 | #: "src/dn/data/GetText.hx" 19 | msgctxt "Context 1" 20 | msgid "Ambiguous" 21 | msgstr "" 22 | 23 | #: "src/dn/data/GetText.hx" 24 | msgctxt "Context 2" 25 | msgid "Ambiguous" 26 | msgstr "" 27 | 28 | #. I'm the commentary 29 | #: "src/dn/legacy/GetText.hx" 30 | msgid "Text with commentary" 31 | msgstr "" 32 | 33 | #. Commentary 34 | #: "src/dn/legacy/GetText.hx" 35 | msgid "Text with parameters: ::param:: and commentary" 36 | msgstr "" 37 | 38 | # CastleDB 39 | #: "res/cdbTest.cdb:texts/#0.text" 40 | msgid "I'm a CDB text" 41 | msgstr "" 42 | 43 | # CastleDB 44 | #. Commentary 45 | #: "res/cdbTest.cdb:texts/#1.text" 46 | msgid "I'm a CDB text with commentary" 47 | msgstr "" 48 | 49 | # CastleDB 50 | #: "res/cdbTest.cdb:texts/#2.text" 51 | msgid "I'm a CDB text with param: ::param::" 52 | msgstr "" 53 | 54 | # CastleDB 55 | #. Commentary 56 | #: "res/cdbTest.cdb:texts/#3.text" 57 | msgid "I'm a CDB text with param: ::param:: and commentary" 58 | msgstr "" 59 | -------------------------------------------------------------------------------- /src/assets/deepnightControllerIcons.aseprite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deepnight/deepnightLibs/2cbad91a45bad941dc08df74f0fe369ced23ca96/src/assets/deepnightControllerIcons.aseprite -------------------------------------------------------------------------------- /src/dn/AbstractEnumFlags.hx: -------------------------------------------------------------------------------- 1 | package dn; 2 | 3 | #if macro 4 | import haxe.macro.Expr; 5 | import haxe.macro.Context; 6 | #end 7 | 8 | class AbstractEnumFlags { 9 | var flagNames : Map; 10 | var flagValues : Map = new Map(); 11 | 12 | function new(flagNames:Map) { 13 | this.flagNames = flagNames; 14 | } 15 | 16 | public inline function has(flag:T) { 17 | return flagValues.exists(flag); 18 | } 19 | 20 | public inline function setOnce(flag:T) { 21 | if( !has(flag) ) { 22 | set(flag); 23 | return true; 24 | } 25 | else 26 | return false; 27 | } 28 | 29 | public inline function set(flag:T, v=true) { 30 | if( v ) 31 | flagValues.set(flag,v); 32 | else 33 | flagValues.remove(flag); 34 | } 35 | 36 | public inline function toggle(flag:T) { 37 | set(flag, !has(flag)); 38 | } 39 | 40 | public inline function getFlagValue(flag:T) { 41 | return flagValues.exists(flag); 42 | } 43 | 44 | public inline function remove(flag:T) { 45 | return flagValues.remove(flag); 46 | } 47 | 48 | public function getActiveFlags() : Array { 49 | var allFlags : Array = []; 50 | for(f in flagValues.keys()) 51 | allFlags.push(f); 52 | return allFlags; 53 | } 54 | 55 | public inline function getFlagName(flag:T) : String { 56 | return flagNames.get(flag); 57 | } 58 | 59 | public function clearAll() { 60 | flagValues = new Map(); 61 | } 62 | 63 | @:keep public function toString() : String { 64 | var out = []; 65 | for(f in flagValues.keys()) 66 | out.push( getFlagName(f) ); 67 | return "["+out.join(",")+"]"; 68 | } 69 | 70 | public function toArray() : Array { 71 | var out = []; 72 | for(f in flagValues.keys()) 73 | out.push(f); 74 | return out; 75 | } 76 | 77 | 78 | public static macro function createFromAbstractIntEnum(abstractEnumType:haxe.macro.Expr) { 79 | var allValues = MacroTools.getAbstractEnumValuesForMacros(abstractEnumType); 80 | 81 | // Check enum underlying type 82 | for(v in allValues) 83 | switch v.valueExpr.expr { 84 | case EConst(CInt(_)): 85 | case _: Context.fatalError("Only abstract enum of Int is supported.", abstractEnumType.pos); 86 | } 87 | 88 | // Build `0=>"ValueName"` expressions 89 | var mapInitExprs = []; 90 | for(v in allValues) 91 | mapInitExprs.push( macro $e{v.valueExpr} => $v{v.name} ); 92 | 93 | // Build Map declaration as `[ 0=>"ValueName", 1=>"ValueName" ]` 94 | var flagNamesExpr : Expr = { pos:Context.currentPos(), expr: EArrayDecl(mapInitExprs) } 95 | return macro @:privateAccess new dn.AbstractEnumFlags( $flagNamesExpr ); 96 | } 97 | 98 | } -------------------------------------------------------------------------------- /src/dn/CdbHelper.hx: -------------------------------------------------------------------------------- 1 | package dn; 2 | 3 | import cdb.Types; 4 | 5 | /* 6 | EXAMPLE: 7 | 8 | var lvl = Data.level.get(Lab); 9 | 10 | for( l in lvl.layers ) { 11 | var tile = hxd.Res.load(l.data.file).toTile(); 12 | var tileSet = lvl.props.getTileset(Data.level, l.data.file); 13 | 14 | for(t in CdbHelper.getAllTiles(l.data, tile, lvl.width, tileSet)) 15 | trace(t); 16 | } 17 | 18 | */ 19 | 20 | private typedef CdbTile = { 21 | var cx : Int; 22 | var cy : Int; 23 | var x : Int; 24 | var y : Int; 25 | var t : Null; 26 | } 27 | 28 | class CdbHelper { 29 | public static function getH2dTile(?sheet:h2d.Tile, t:TilePos) : h2d.Tile { 30 | if( sheet==null ) 31 | sheet = hxd.Res.load(t.file).toTile(); 32 | return sheet.sub( t.x*t.size, t.y*t.size, t.size, t.size ); 33 | } 34 | 35 | public static function isTileLayer(l:TileLayer) return l.data.decode()[0]!=0xFFFF; 36 | public static function isObjectLayer(l:TileLayer) return l.data.decode()[0]==0xFFFF; 37 | 38 | public static function getLayerTiles(l:TileLayer, sheet:h2d.Tile, levelWid:Int, tileSetProps:cdb.Data.TilesetProps) : Array { 39 | var lContent = l.data.decode(); 40 | 41 | var tiles : Array = []; 42 | if( lContent[0]==0xFFFF ) { 43 | // Object layer 44 | lContent.shift(); 45 | for( i in 0...Std.int(lContent.length/3) ) { 46 | var x = lContent[i*3]; 47 | var y = lContent[i*3+1]; 48 | var id = lContent[i*3+2]; 49 | tiles.push( { 50 | cx : Std.int(x/l.size), 51 | cy : Std.int(y/l.size), 52 | x : x, 53 | y : y, 54 | t : getObjectTile(sheet, l.size, tileSetProps, id), 55 | }); 56 | } 57 | } 58 | else { 59 | // Tile layer 60 | for(i in 0...lContent.length) { 61 | if( lContent[i]>0 ) { 62 | var pt = idxToPt(i, levelWid); 63 | tiles.push( { 64 | cx : pt.x, 65 | cy : pt.y, 66 | x : pt.x*l.size, 67 | y : pt.y*l.size, 68 | t : getTile(sheet, l.size, lContent[i]-1), 69 | } ); 70 | } 71 | } 72 | } 73 | return tiles; 74 | } 75 | 76 | public static function getLayerPoints(l:TileLayer, levelWid:Int) : Array { 77 | var lContent = l.data.decode(); 78 | 79 | var tiles : Array = []; 80 | if( lContent[0]!=0xFFFF ) { 81 | // Tile layer 82 | for(i in 0...lContent.length) { 83 | if( lContent[i]>0 ) { 84 | var pt = idxToPt(i, levelWid); 85 | tiles.push( { 86 | cx : pt.x, 87 | cy : pt.y, 88 | x : pt.x*l.size, 89 | y : pt.y*l.size, 90 | t : null, 91 | } ); 92 | } 93 | } 94 | } 95 | return tiles; 96 | } 97 | 98 | 99 | static function getTile(sheet:h2d.Tile, tileSize:Int, idx:Int) : h2d.Tile { 100 | var line = Std.int(sheet.width/tileSize); 101 | var y = Std.int(idx/line); 102 | return sheet.sub( (idx-y*line)*tileSize, y*tileSize, tileSize, tileSize ); 103 | } 104 | 105 | static function getObjectTile(source:h2d.Tile, tileSize:Int, props:cdb.Data.TilesetProps, idx:Int) : h2d.Tile { 106 | var flip = false; 107 | if( idx&0x8000!=0 ) { 108 | idx-=0x8000; 109 | flip = true; 110 | } 111 | 112 | var line = Std.int(source.width/tileSize); 113 | var y = Std.int(idx/line); 114 | var x = (idx-y*line); 115 | 116 | var t = source.sub(x*tileSize, y*tileSize, tileSize,tileSize); 117 | 118 | // Apply object size 119 | for(s in props.sets) 120 | if( s.x==x && s.y==y ) { 121 | t.setSize(s.w*tileSize, s.h*tileSize); 122 | break; 123 | } 124 | 125 | if( flip ) { 126 | t.flipX(); 127 | t.dx += t.width; 128 | } 129 | 130 | return t; 131 | } 132 | 133 | 134 | static inline function idxToPt(id:Int, wid:Int) return { 135 | x : idxToX(id,wid), 136 | y : idxToY(id,wid), 137 | } 138 | static inline function idxToX(id:Int, wid:Int) return id - idxToY(id,wid)*wid; 139 | static inline function idxToY(id:Int, wid:Int) return Std.int(id/wid); 140 | 141 | } -------------------------------------------------------------------------------- /src/dn/Changelog.hx: -------------------------------------------------------------------------------- 1 | package dn; 2 | 3 | 4 | /** 5 | Markdown Changelog parser 6 | 7 | Expected format and entry order: 8 | 9 | ```markdown 10 | # 0.9 - Some title 11 | - ...note... 12 | - ...note... 13 | 14 | # 0.8 15 | - ...note... 16 | - ...note... 17 | 18 | # 0.7.2-alpha - Another title 19 | - ...note... 20 | - ...note... 21 | ``` 22 | **/ 23 | 24 | class Changelog { 25 | /** 26 | Customizable title parser to recognize lines with a new version number 27 | **/ 28 | public static var VERSION_TITLE_REG = ~/^[ \t]*#[ \t]+v?([0-9]+[0-9.a-z\-]*)[\- \t]*(.*)$/gim; 29 | 30 | /** 31 | Changelog entries 32 | **/ 33 | public var entries : Array; 34 | 35 | /** Latest entry **/ 36 | public var latest(get,never) : Null; 37 | inline function get_latest() return entries.length==0 ? null : entries[0]; 38 | 39 | /** Latest entry **/ 40 | public var oldest(get,never) : Null; 41 | inline function get_oldest() return entries.length==0 ? null : entries[ entries.length-1 ]; 42 | 43 | /** 44 | Version numbers should comply to the SemVer format. 45 | See parse() for more informations. 46 | **/ 47 | public function new(markdown:String) { 48 | parse(markdown); 49 | } 50 | 51 | /** 52 | Return a short description of the changelog data. 53 | **/ 54 | @:keep 55 | public function toString() { 56 | return 'Changelog, ${entries.length} entries = [' 57 | + entries.map(function(v) { 58 | return '${v.version.full}, "${v.title}", ${v.notEmptyNoteLines.length} lines'; 59 | }).join("], [") 60 | + "]"; 61 | } 62 | 63 | public static inline function fromString(markdown:String) { 64 | return new Changelog(markdown); 65 | } 66 | 67 | /** 68 | Version numbers should comply to the SemVer format. 69 | 70 | Changelog markdown should comply to the example below. Parser can be customized by 71 | changing static RegEx. 72 | 73 | \#\# 0.1 - Some title 74 | 75 | Some markdown... 76 | 77 | Another markdown... 78 | 79 | \#\# 0.0.1-alpha - Another title 80 | 81 | A markdown paragraph... 82 | **/ 83 | public function parse(markdown:String) { 84 | entries = []; 85 | 86 | var endl = markdown.indexOf("\r\n")>=0 ? "\r\n" : "\n"; 87 | var lines = markdown.split( endl ); 88 | var cur : ChangelogEntry = null; 89 | for(l in lines) { 90 | if( VERSION_TITLE_REG.match(l) ) { 91 | var rawVersion = VERSION_TITLE_REG.matched(1); 92 | 93 | // Parse version number according to SemVer format 94 | if( !Version.isValid(rawVersion, true) ) 95 | throw 'Version number "$rawVersion" in changelog do not comply to SemVer semantics'; 96 | 97 | var ver = new Version(rawVersion); 98 | var title = VERSION_TITLE_REG.matched(2)=="" ? null : VERSION_TITLE_REG.matched(2); 99 | cur = { 100 | version: ver, 101 | title: title, 102 | displayTitle: title==null ? ver.toString() : '${ver.toString()} - $title', 103 | allNoteLines: [], 104 | notEmptyNoteLines: [], 105 | } 106 | 107 | entries.push(cur); 108 | continue; 109 | } 110 | 111 | if( cur==null ) 112 | continue; 113 | 114 | cur.allNoteLines.push(l); 115 | var trim = trimLine(l); 116 | if( trim.length>0 ) 117 | cur.notEmptyNoteLines.push(l); 118 | } 119 | 120 | entries.sort( function(a,b) return -a.version.compareEverything( b.version ) ); 121 | } 122 | 123 | function trimLine(l:String) { 124 | while( l.length>0 && ( l.charAt(0)==" " || l.charAt(0)=="\t" ) ) 125 | l = l.substr(1); 126 | 127 | while( l.length>0 && ( l.charAt(l.length-1)==" " || l.charAt(l.length-1)=="\t" ) ) 128 | l = l.substr(0, l.length-1); 129 | 130 | return l; 131 | } 132 | 133 | 134 | #if deepnightLibsTests 135 | public static function test() { 136 | var markDown = " 137 | # 0.9 - Not in proper position 138 | 139 | Some note 140 | 141 | # 1 - Release 142 | 143 | Some note 144 | 145 | - list 1 146 | - list 2 147 | 148 | 149 | # 0.7-beta - Some update 150 | 151 | Some note 152 | 153 | # 0.1-alpha - not in the right position 154 | markdown 155 | Some note 156 | 157 | # 0.2-beta - Going beta 158 | 159 | Some note 160 | "; 161 | 162 | var c = new Changelog( markDown ); 163 | 164 | CiAssert.isTrue( c.entries.length>0 ); 165 | 166 | CiAssert.isNotNull( c.latest ); 167 | CiAssert.isTrue( c.latest.version.full == "1.0.0" ); 168 | CiAssert.isTrue( c.latest.title == "Release" ); 169 | CiAssert.isTrue( c.latest.allNoteLines.length==7 ); 170 | CiAssert.isTrue( c.latest.notEmptyNoteLines.length==3 ); 171 | 172 | CiAssert.isNotNull( c.oldest ); 173 | CiAssert.isTrue( c.oldest.version.full == "0.1.0-alpha" ); 174 | } 175 | #end 176 | } 177 | 178 | 179 | 180 | 181 | typedef ChangelogEntry = { 182 | /** 183 | Version using SemVer semantic x[.y.z-label] 184 | See: https://semver.org/ 185 | **/ 186 | var version : dn.Version; 187 | 188 | /** 189 | Raw version title 190 | **/ 191 | var title: Null; 192 | 193 | /** 194 | If title is not null: "x.y.z" - "Title string" 195 | Otherwise: "x.y.z" 196 | **/ 197 | var displayTitle: Null; 198 | 199 | /** 200 | Markdown description lines 201 | **/ 202 | var allNoteLines : Array; 203 | 204 | /** 205 | Markdown description lines 206 | **/ 207 | var notEmptyNoteLines : Array; 208 | } 209 | 210 | -------------------------------------------------------------------------------- /src/dn/Chrono.hx: -------------------------------------------------------------------------------- 1 | package dn; 2 | 3 | /** 4 | Basic "chronometer" class. 5 | - `init()` to start a session 6 | - to measure the time of a block, either: 7 | - call `start("someId")` and `stop("someId")` around the code block 8 | - or just call `start("someId",true)` (which stops all previous chrono before starting a new one) at the beginning of each block you need to measure 9 | - `printResults()` or `getResultsStr()` to end session 10 | **/ 11 | 12 | class Chrono { 13 | /** Precision **/ 14 | public static var DECIMALS = 4; 15 | public static var COLORS_LOW = { timeThreshold:0.05, col:new Col("#009c44") } 16 | public static var COLORS_HIGH = { timeThreshold:0.50, col:new Col("#a10000") } 17 | 18 | static var all : Array = []; 19 | static var results : Array = []; 20 | 21 | 22 | /** Init chrono session **/ 23 | public static inline function init() { 24 | all = []; 25 | results = []; 26 | } 27 | 28 | 29 | /** 30 | Stop every chrono then return results as an Array of Strings (one line per result) 31 | **/ 32 | public static inline function getResultsStr() : Array { 33 | stopAll(); 34 | return results.map( (r)->r.toString() ); 35 | } 36 | 37 | /** 38 | Stop every chrono then print results using `printer()` method (defaults to `trace()`). 39 | **/ 40 | public static inline function printResults(init=true) { 41 | stopAll(); 42 | // var res = getResultsStr(); 43 | if( results.length>1 ) 44 | printer("--Chrono--"); 45 | for(r in results) 46 | printer(r.toString(), r.getColor()); 47 | 48 | if( init ) 49 | Chrono.init(); 50 | } 51 | 52 | 53 | /** 54 | Print something to output (default is `trace()`). This method can be replaced by a custom method. 55 | **/ 56 | public static dynamic function printer(str:String, ?col:dn.Col) { 57 | #if sys 58 | Sys.println(str); 59 | #elseif js 60 | if( col!=null ) 61 | js.html.Console.log("%c"+str, "color:"+col.toHex()); 62 | else 63 | js.html.Console.log(str); 64 | #else 65 | trace(str); 66 | #end 67 | } 68 | 69 | /** 70 | Start a chrono 71 | **/ 72 | public static inline function start(?id:String, stopAllOthers=false) { 73 | if( stopAllOthers ) 74 | stopAll(); 75 | else if( id!=null ) 76 | stopId(id); 77 | else 78 | stopLast(); 79 | 80 | all.push( new ChronoInstance(id) ); 81 | } 82 | 83 | /** 84 | Start a quick chrono, or stop it and print its result immediately. 85 | **/ 86 | public static inline function quick(?id:String) { 87 | if( all.length==0 || id!=null && !exists(id) ) 88 | start(id,true); 89 | else 90 | printResults(); 91 | } 92 | 93 | /** 94 | Return TRUE if a chrono with this `id` exists 95 | **/ 96 | public static function exists(id:String) { 97 | for(c in all) 98 | if( c.id==id ) 99 | return true; 100 | return false; 101 | } 102 | 103 | 104 | public static function stopLast() : Float { 105 | var last = all.pop(); 106 | last.stop(); 107 | results.push(last); 108 | return last.elapsedS; 109 | } 110 | 111 | public static function stopId(id:String) : Float { 112 | for(i in 0...all.length) 113 | if( all[i].id==id ) { 114 | var c = all[i]; 115 | c.stop(); 116 | results.push(c); 117 | all.splice(i,1); 118 | return c.elapsedS; 119 | } 120 | return 0; 121 | } 122 | 123 | public static function stopAll() { 124 | for(c in all) { 125 | c.stop(); 126 | results.push(c); 127 | } 128 | all = []; 129 | } 130 | } 131 | 132 | 133 | private class ChronoInstance { 134 | public var id(default,null): Null; 135 | var startStamp: Float; 136 | var stopStamp: Float = -1; 137 | 138 | public var elapsedS(get,never) : Float; 139 | inline function get_elapsedS() { 140 | return stopStamp<0 ? haxe.Timer.stamp()-startStamp : stopStamp-startStamp; 141 | } 142 | 143 | public inline function new(?id) { 144 | this.id = id; 145 | startStamp = haxe.Timer.stamp(); 146 | } 147 | 148 | public inline function isStopped() { 149 | return stopStamp>=0; 150 | } 151 | 152 | public function getColor() : Null { 153 | if( !isStopped() ) 154 | return null; 155 | else 156 | return Chrono.COLORS_LOW.col.to( Chrono.COLORS_HIGH.col, M.subRatio(elapsedS, Chrono.COLORS_LOW.timeThreshold, Chrono.COLORS_HIGH.timeThreshold) ); 157 | } 158 | 159 | public inline function stop() { 160 | if( !isStopped() ) 161 | stopStamp = haxe.Timer.stamp(); 162 | } 163 | 164 | @:keep public function toString() { 165 | return 166 | ( id=="" || id==null ? "" : id+" => " ) 167 | + M.pretty(elapsedS, Chrono.DECIMALS)+"s" 168 | + ( !isStopped() ? " (running)" : "" ); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/dn/CiAssert.hx: -------------------------------------------------------------------------------- 1 | package dn; 2 | 3 | #if macro 4 | import haxe.macro.Expr; 5 | import haxe.macro.Context; 6 | using haxe.macro.TypeTools; 7 | #end 8 | 9 | class CiAssert { 10 | /** If FALSE, only errors are printed **/ 11 | public static var VERBOSE = false; 12 | 13 | public static macro function isTrue(code:Expr) { 14 | return macro { 15 | if( ${buildIsTrueExpr(code)} ) 16 | dn.CiAssert.printOk( $v{getCodeStr(code)} ); 17 | else 18 | dn.CiAssert.fail( $v{getFilePos()}, $v{getCodeStr(code)}, "This expression should be TRUE"); 19 | }; 20 | } 21 | 22 | public static macro function equals(codeA:Expr, codeB:Expr) { 23 | var eCheck : Expr = { 24 | expr: EBinop(OpEq, codeA, codeB), 25 | pos: Context.currentPos(), 26 | } 27 | 28 | return macro { 29 | if( $eCheck ) 30 | dn.CiAssert.printOk( $v{getCodeStr(eCheck)} ); 31 | else { 32 | dn.CiAssert.fail( 33 | $v{getFilePos()}, 34 | $v{getCodeStr(eCheck)}, 35 | "These 2 expressions should be EQUAL", 36 | [ $codeA+" != "+$codeB ] 37 | ); 38 | } 39 | }; 40 | } 41 | 42 | public static macro function isFalse(code:Expr) { 43 | return macro { 44 | if( !${buildIsTrueExpr(code)} ) 45 | dn.CiAssert.printOk( $v{getCodeStr(code)} ); 46 | else 47 | dn.CiAssert.fail( $v{getFilePos()}, $v{getCodeStr(code)}, "This expression should be FALSE"); 48 | }; 49 | } 50 | 51 | public static macro function noException(desc:String, code:Expr) { 52 | return macro { 53 | try { 54 | $code; 55 | dn.CiAssert.printOk($v{getDescWithPrefix(desc)} ); 56 | } 57 | catch(e:Dynamic) { 58 | dn.CiAssert.fail( $v{getFilePos()}, $v{getDescWithPrefix(desc)}, "This expression should thrown NO exception (caught \""+e+"\")"); 59 | } 60 | }; 61 | } 62 | 63 | public static macro function isNotNull(code:Expr) { 64 | return macro { 65 | if( ($code) != null ) 66 | dn.CiAssert.printOk( $v{getCodeStr(code)} ); 67 | else 68 | dn.CiAssert.fail( $v{getFilePos()}, $v{getCodeStr(code)}, "This expression should NOT be NULL"); 69 | }; 70 | } 71 | 72 | 73 | #if macro 74 | // Build expr of: "code==true" 75 | static function buildIsTrueExpr(code:Expr) : Expr { 76 | var eCheck : Expr = { 77 | expr: EBinop(OpEq, code, macro true), 78 | pos: Context.currentPos(), 79 | } 80 | 81 | // Does "code" actually returns a Bool? 82 | try Context.typeExpr(eCheck) 83 | catch(err:Dynamic) { 84 | Context.fatalError('$err (this assertion should return a Bool)', code.pos); 85 | } 86 | 87 | return eCheck; 88 | } 89 | 90 | // Print code expr in human-readable fashion 91 | static function getCodeStr(code:Expr, prefix=true) : String { 92 | var printer = new haxe.macro.Printer(); 93 | var codeStr = printer.printExpr(code); 94 | codeStr = StringTools.replace(codeStr,"\n",""); 95 | codeStr = StringTools.replace(codeStr,"\t"," "); 96 | if( codeStr.length>=90 ) 97 | codeStr = codeStr.substr(0,10)+"... ..."+codeStr.substr(-80); 98 | 99 | var build = Context.defined("js") ? "JS" 100 | : Context.defined("hl") ? "HL" 101 | : Context.defined("neko") ? "Neko" 102 | : "Unknown"; 103 | 104 | return prefix 105 | ? '[$build|${Context.getLocalModule()}] $codeStr' 106 | : codeStr; 107 | } 108 | 109 | static function getDescWithPrefix(str:String) : String { 110 | var build = Context.defined("js") ? "JS" 111 | : Context.defined("hl") ? "HL" 112 | : Context.defined("neko") ? "Neko" 113 | : "Unknown"; 114 | 115 | return '[$build|${Context.getLocalModule()}] "$str"'; 116 | } 117 | #end 118 | 119 | #if macro 120 | static function getFilePos() { 121 | var pos = Context.getPosInfos( Context.currentPos() ); 122 | var fi = sys.io.File.read(pos.file); 123 | var line = fi.readString(pos.min).split("\n").length; 124 | fi.close(); 125 | return { file:pos.file, line:line } 126 | } 127 | #end 128 | 129 | @:noCompletion 130 | public static inline function printOk(v:Dynamic) { 131 | if( VERBOSE ) 132 | Lib.println(Std.string(v)+" "); 133 | } 134 | 135 | public static inline function printIfVerbose(v:Dynamic) { 136 | if( VERBOSE ) 137 | Lib.println(v); 138 | } 139 | 140 | @:noCompletion 141 | public static function fail(filePos:{file:String, line:Int}, desc:String, reason:String, ?extraInfos:Array) { 142 | var desc = Std.string(desc); 143 | var sep = [ for(i in 0...desc.length+11) "*" ]; 144 | 145 | Lib.println(sep.join("")); 146 | Lib.println(desc+" "); 147 | if( extraInfos!=null ) 148 | for( str in extraInfos ) 149 | Lib.println("\t"+str); 150 | Lib.println("ERROR: "+reason); 151 | Lib.println(sep.join("")); 152 | 153 | // Stop 154 | #if js 155 | throw new js.lib.Error('Failed in ${filePos.file}'); 156 | #elseif flash 157 | throw reason; 158 | #else 159 | Sys.stderr().writeString('${filePos.file}:${filePos.line}: characters 1-999 : $reason\n'); 160 | Sys.exit(1); 161 | #end 162 | } 163 | } -------------------------------------------------------------------------------- /src/dn/Delayer.hx: -------------------------------------------------------------------------------- 1 | package dn; 2 | 3 | 4 | private class Task { // Classes are faster 5 | public var frames : Float; 6 | public var id : String; 7 | public var cb : Void->Void; 8 | 9 | public inline function new(id, frames,cb) { // Inline new classes are faster faster 10 | this.frames = frames; 11 | this.cb = cb; 12 | this.id = id; 13 | } 14 | 15 | @:keep public function toString() return frames+"f "+cb; 16 | } 17 | 18 | class Delayer { 19 | var delays : Array = []; 20 | var fps : Float; 21 | 22 | public function new(fps:Float) { 23 | this.fps = fps; 24 | } 25 | 26 | @:keep public function toString() { 27 | return "Delayer(timers=" + delays.map( d->M.pretty(d.frames/fps,1)+"s" ).join(",") + ")"; 28 | } 29 | 30 | public inline function isDestroyed() return delays == null; 31 | 32 | @:deprecated("Use dispose() instead") @:noCompletion 33 | public inline function destroy() { 34 | dispose(); 35 | } 36 | 37 | public function dispose() { 38 | delays = null; 39 | } 40 | 41 | // public function skip() { 42 | // var limit = delays.length+100; 43 | // while( delays.length>0 && limit-->0 ) { 44 | // var d = delays.shift(); 45 | // d.cb(); 46 | // d.cb = null; 47 | // } 48 | // } 49 | 50 | /** Completes all tasks immediately (tasks callbacks will be called) */ 51 | public function completeEverything() { 52 | var all = delays.copy(); 53 | delays = []; 54 | for(d in all) 55 | d.cb(); 56 | } 57 | 58 | /** Cancels all tasks (their callbacks won't be called) */ 59 | public function cancelEverything() { 60 | delays = []; 61 | } 62 | 63 | public function hasId(id:String) { 64 | for(e in delays) 65 | if( e.id==id ) 66 | return true; 67 | return false; 68 | } 69 | 70 | public function cancelById(id:String) { 71 | var i = 0; 72 | while( i b.frames ? 1 94 | : 0; 95 | } 96 | 97 | public function addMs(?id:String, cb:Void->Void, ms:Float) { 98 | if( ms<=0 ) 99 | cb(); 100 | else { 101 | delays.push( new Task( id, fps*ms/1000, cb) ); 102 | haxe.ds.ArraySort.sort(delays, cmp); 103 | } 104 | } 105 | 106 | public function addS(?id:String, cb:Void->Void, sec:Float) { 107 | if( sec<=0 ) 108 | cb(); 109 | else { 110 | delays.push( new Task( id, fps*sec, cb) ); 111 | haxe.ds.ArraySort.sort(delays, cmp); 112 | } 113 | } 114 | 115 | public function addF(?id:String, cb:Void->Void, frames:Float) { 116 | if( frames<=0 ) 117 | cb(); 118 | else { 119 | delays.push( new Task( id, frames, cb ) ); 120 | haxe.ds.ArraySort.sort(delays, cmp); 121 | } 122 | } 123 | 124 | public inline function nextFrame(cb:Void->Void) { 125 | addF(cb, 0.000001); 126 | } 127 | 128 | public inline function hasAny() return !isDestroyed() && delays.length>0; 129 | 130 | public function update(tmod:Float) { 131 | var i = 0; 132 | while( idone=true, 2); 154 | CiAssert.isTrue( delayer.hasId("test") ); 155 | CiAssert.isFalse( done ); 156 | 157 | for(i in 0...fps) delayer.update(1); // 1s 158 | CiAssert.isFalse( done ); 159 | 160 | for(i in 0...fps) delayer.update(1); // 2s 161 | CiAssert.isTrue( done ); 162 | CiAssert.isFalse( delayer.hasId("test") ); 163 | 164 | // Cancelling 165 | delayer.addF("test", ()->throw "exception", 1); 166 | delayer.cancelById("test"); 167 | delayer.update(1); 168 | 169 | // Completing all 170 | var n = 0; 171 | delayer.addF("a", ()->n++, 1); 172 | delayer.addF("b", ()->n++, 99); 173 | delayer.addF("c", ()->n++, 99); 174 | delayer.update(1); 175 | CiAssert.equals( n, 1 ); 176 | delayer.completeEverything(); 177 | CiAssert.equals( n, 3 ); 178 | 179 | // Next frame 180 | var done = false; 181 | delayer.nextFrame( ()->done=true ); 182 | CiAssert.isFalse( done ); 183 | delayer.update(1); 184 | CiAssert.isTrue( done ); 185 | } 186 | #end 187 | } 188 | -------------------------------------------------------------------------------- /src/dn/FileTools.hx: -------------------------------------------------------------------------------- 1 | package dn; 2 | 3 | class FileTools { 4 | #if sys 5 | public static function deleteDirectoryRec(path:String) { 6 | var fp = FilePath.fromDir(path); 7 | fp.useSlashes(); 8 | path = fp.directory; 9 | 10 | if( !sys.FileSystem.exists(path) ) 11 | return; 12 | 13 | try { 14 | var all = listAllFilesRec(path); 15 | 16 | // Remove files 17 | for(f in all.files) 18 | sys.FileSystem.deleteFile(f); 19 | 20 | // Remove folders 21 | all.dirs.reverse(); 22 | for(path in all.dirs) 23 | sys.FileSystem.deleteDirectory(path); 24 | } 25 | catch( err:Dynamic ) { 26 | throw "Couldn't delete directory "+path+" (ERR: "+err+")"; 27 | } 28 | } 29 | #end 30 | 31 | #if sys 32 | public static function copyDirectoryRec(from:String, to:String, ?ignoredNames:Array, ?ignoredExts:Array) { 33 | var to = dn.FilePath.extractDirectoryWithSlash(to,false); 34 | var all = listAllFilesRec(from, ignoredNames, ignoredExts); 35 | 36 | var dirName = FilePath.fromDir(from).getLastDirectory(); 37 | if( dirName=="." || dirName==".." ) 38 | throw "Unsupported operation with a path terminated by dots ("+from+")"; 39 | 40 | sys.FileSystem.createDirectory(to+dirName); 41 | to+=dirName+"/"; 42 | 43 | // Create directory structure 44 | for(d in all.dirs) { 45 | if( d.indexOf(from)==0 ) 46 | d = d.substr(from.length); 47 | while( d.charAt(0)=="/" ) 48 | d = d.substr(1); 49 | 50 | if( d.length==0 ) 51 | continue; 52 | 53 | sys.FileSystem.createDirectory(to+d); 54 | } 55 | 56 | // Copy files 57 | for(f in all.files) { 58 | var target = f; 59 | if( target.indexOf(from)==0 ) 60 | target = target.substr(from.length); 61 | while( target.charAt(0)=="/" ) 62 | target = target.substr(1); 63 | 64 | sys.io.File.copy(f, to+target); 65 | } 66 | } 67 | #end 68 | 69 | #if sys 70 | /** 71 | Return a list of directories and files in given dir, including their full path. 72 | `ignoredNames` is used to discard specific file or dir names. 73 | `ignoredExts` is used to discard specific file extensions (extension should be provided without leading dot, eg. ["tmp"]) 74 | **/ 75 | public static function listAllFilesRec(path:String, ?ignoredNames:Array, ?ignoredExts:Array) : { dirs:Array, files:Array } { 76 | // List elements 77 | var pendingDirs = [path]; 78 | var dirs = []; 79 | var files = []; 80 | while( pendingDirs.length>0 ) { 81 | var cur = pendingDirs.shift(); 82 | dirs.push( FilePath.fromDir(cur).full ); 83 | for( e in sys.FileSystem.readDirectory(cur) ) { 84 | if( ignoredNames!=null && ignoredNames.indexOf(e)>=0 ) 85 | continue; 86 | 87 | if( ignoredExts!=null && ignoredExts.indexOf(FilePath.extractExtension(e))>=0 ) 88 | continue; 89 | 90 | if( sys.FileSystem.isDirectory(cur+"/"+e) ) 91 | pendingDirs.push(cur+"/"+e); 92 | else 93 | files.push( FilePath.fromDir(cur+"/"+e).full ); 94 | } 95 | } 96 | return { 97 | dirs: dirs, 98 | files: files, 99 | } 100 | } 101 | #end 102 | 103 | 104 | #if sys 105 | public static function zipFolder(zipPath:String, dirPath:String, ?onProgress:(fileName:String, size:Int)->Void) { 106 | // List entries 107 | var entries : List = new List(); 108 | var pendingDirs = [ dirPath ]; 109 | while( pendingDirs.length>0 ) { 110 | var cur = pendingDirs.shift(); 111 | for( fName in sys.FileSystem.readDirectory(cur) ) { 112 | var path = cur+"/"+fName; 113 | if( sys.FileSystem.isDirectory(path) ) { 114 | pendingDirs.push(path); 115 | entries.add({ 116 | fileName: path.substr(dirPath.length+1) + "/", 117 | fileSize: 0, 118 | fileTime: sys.FileSystem.stat(path).ctime, 119 | data: haxe.io.Bytes.alloc(0), 120 | dataSize: 0, 121 | compressed: false, 122 | crc32: null, 123 | }); 124 | } 125 | else { 126 | var bytes = sys.io.File.getBytes(path); 127 | entries.add({ 128 | fileName: path.substr(dirPath.length+1), 129 | fileSize: sys.FileSystem.stat(path).size, 130 | fileTime: sys.FileSystem.stat(path).ctime, 131 | data: bytes, 132 | dataSize: bytes.length, 133 | compressed: false, 134 | crc32: null, 135 | }); 136 | } 137 | } 138 | } 139 | 140 | // Zip entries 141 | var out = new haxe.io.BytesOutput(); 142 | for(e in entries) 143 | if( e.data.length>0 ) { 144 | if( onProgress!=null ) 145 | onProgress(e.fileName, e.fileSize); 146 | e.crc32 = haxe.crypto.Crc32.make(e.data); 147 | haxe.zip.Tools.compress(e,9); 148 | } 149 | var w = new haxe.zip.Writer(out); 150 | w.write(entries); 151 | sys.io.File.saveBytes(zipPath, out.getBytes()); 152 | } 153 | #end 154 | } 155 | -------------------------------------------------------------------------------- /src/dn/Gc.hx: -------------------------------------------------------------------------------- 1 | package dn; 2 | 3 | /** 4 | Abstract garbage collector controller 5 | (supports Hashlink only for now) 6 | **/ 7 | class Gc { 8 | static var _active = true; 9 | 10 | /** Return TRUE if GC management is supported **/ 11 | public static function isSupported() { 12 | #if hl 13 | return true; 14 | #else 15 | return false; 16 | #end 17 | } 18 | 19 | /** Return TRUE if GC is currently running. On unsupported platforms, this always returns FALSE. **/ 20 | public static inline function isActive() return _active && isSupported(); 21 | 22 | /** Enable or disable the GC **/ 23 | public static inline function setState(active:Bool) { 24 | #if hl 25 | hl.Gc.enable(active); 26 | _active = active; 27 | #end 28 | } 29 | 30 | /** Enable the GC **/ 31 | public static inline function enable() setState(true); 32 | 33 | /** Disable the GC **/ 34 | public static inline function disable() setState(false); 35 | 36 | /** Try to force a GC sweeping immediately **/ 37 | public static inline function runNow() { 38 | #if hl 39 | hl.Gc.enable(true); 40 | hl.Gc.major(); 41 | hl.Gc.enable(_active); 42 | #end 43 | } 44 | 45 | public static inline function setFlag(f:hl.Gc.GcFlag, v:Bool) { 46 | var flags : haxe.EnumFlags = new haxe.EnumFlags(); 47 | if( v ) 48 | flags.set(f); 49 | hl.Gc.flags = flags; 50 | } 51 | 52 | 53 | /** Return currently "currently allocated memory" **/ 54 | public static inline function getCurrentMem() : Float { 55 | #if hl 56 | var _ = 0., v = 0.; 57 | @:privateAccess hl.Gc._stats(_, _, v); 58 | return v; 59 | #else 60 | return 0; 61 | #end 62 | } 63 | 64 | /** Return current "allocation count" **/ 65 | public static inline function getAllocationCount() : Float { 66 | #if hl 67 | var _ = 0., v = 0.; 68 | @:privateAccess hl.Gc._stats(_, v, _); 69 | return v; 70 | #else 71 | return 0; 72 | #end 73 | } 74 | 75 | /** Return current "total allocated" **/ 76 | public static inline function getTotalAllocated() : Float { 77 | #if hl 78 | var _ = 0., v = 0.; 79 | @:privateAccess hl.Gc._stats(v, _, _); 80 | return v; 81 | #else 82 | return 0; 83 | #end 84 | } 85 | 86 | } -------------------------------------------------------------------------------- /src/dn/Identify.hx: -------------------------------------------------------------------------------- 1 | package dn; 2 | 3 | // Inspired by: https://github.com/maitag/whatformat 4 | // Signatures: https://en.wikipedia.org/wiki/List_of_file_signatures 5 | 6 | enum IdentifyFormat { 7 | Unknown; 8 | // Images 9 | Png; 10 | Jpeg; 11 | Gif; 12 | Bmp; 13 | Aseprite; 14 | } 15 | 16 | private typedef FileHeader = { id:IdentifyFormat, ?skipBytes:Int, magic:Array }; 17 | 18 | class Identify { 19 | static var headers : Array = [ 20 | // Note: "-1" means "any byte" 21 | { id:Png, magic:[0x89,0x50,0x4E,0x47,0x0D,0x0A,0x1A,0x0A] }, 22 | 23 | { id:Gif, magic:[0x47,0x49,0x46,0x38,0x37,0x61] }, // Gif 87a 24 | { id:Gif, magic:[0x47,0x49,0x46,0x38,0x39,0x61] }, // Gif 89a 25 | 26 | { id:Jpeg, magic:[0xFF,0xD8,0xFF] }, // Jpeg raw 27 | { id:Jpeg, magic:[0xFF,0xD8,0xFF,0xDB] }, // Jpeg raw 28 | { id:Jpeg, magic:[0xFF,0xD8,0xFF,0xE0,-1,-1,0x4A,0x46,0x49,0x46,0x00,0x01] }, // Jpeg JFIF 29 | { id:Jpeg, magic:[0xFF,0xD8,0xFF,0xE1,-1,-1,0x45,0x78,0x69,0x66,0x00,0x00] }, // Jpeg EXIF 30 | 31 | { id:Aseprite, skipBytes:4, magic:[0xE0,0xA5] }, 32 | 33 | { id:Bmp, magic:[0x42,0x4d] }, 34 | ]; 35 | 36 | public static function getType(b:haxe.io.Bytes) : IdentifyFormat { 37 | if( b==null ) 38 | return Unknown; 39 | 40 | for(h in headers) 41 | if( matchHeader(b, h) ) 42 | return h.id; 43 | 44 | return Unknown; 45 | } 46 | 47 | public static function is64BitsExe(first1024bytes:haxe.io.Bytes) { 48 | if( first1024bytes==null ) 49 | return false; 50 | 51 | var header = "PE".split("").map(function(c) return c.charCodeAt(0)); 52 | var i = 0; 53 | while( i=b.length || h.magic[i]>=0 && h.magic[i] != b.get(i+skip) ) 67 | return false; 68 | 69 | return true; 70 | } 71 | } -------------------------------------------------------------------------------- /src/dn/ImageDecoder.hx: -------------------------------------------------------------------------------- 1 | package dn; 2 | 3 | typedef DecodedImage = { 4 | var decodedBytes: haxe.io.Bytes; // pixels in BGRA format 5 | var width: Int; 6 | var height: Int; 7 | } 8 | 9 | class ImageDecoder { 10 | public static var lastError : Null; 11 | 12 | public static function decode(fileContent:haxe.io.Bytes) : Null { 13 | lastError = null; 14 | return try switch dn.Identify.getType(fileContent) { 15 | case Png: decodePng(fileContent); 16 | case Gif: decodeGif(fileContent); 17 | case Jpeg: decodeJpeg(fileContent); 18 | 19 | case Aseprite: 20 | #if heaps_aseprite 21 | decodeAsepriteMainTexture(fileContent); 22 | #else 23 | throw('[ImageDecoder] Aseprite decoding requires both "heaps-aseprite" and "ase" libs (run "haxelib install ase" and "haxelib install heaps-aseprite").'); 24 | null; 25 | #end 26 | 27 | case _: null; 28 | } 29 | catch( err:String ) { 30 | lastError = err; 31 | return null; 32 | } 33 | } 34 | 35 | #if heaps 36 | public static function decodePixels(fileContent:haxe.io.Bytes) : Null { 37 | var img = decode(fileContent); 38 | return img==null ? null : new hxd.Pixels(img.width, img.height, img.decodedBytes, BGRA); 39 | } 40 | 41 | public static function decodeTexture(fileContent:haxe.io.Bytes) : Null { 42 | var pixels = decodePixels(fileContent); 43 | return pixels==null ? null : h3d.mat.Texture.fromPixels(pixels); 44 | } 45 | 46 | public static function decodeTile(fileContent:haxe.io.Bytes) : Null { 47 | var pixels = decodePixels(fileContent); 48 | return pixels==null ? null : h2d.Tile.fromPixels(pixels); 49 | } 50 | #end 51 | 52 | 53 | 54 | 55 | static function decodePng(b:haxe.io.Bytes) : Null { 56 | try { 57 | var i = new haxe.io.BytesInput(b); 58 | var reader = new format.png.Reader(i); 59 | var data = reader.read(); 60 | 61 | // Read size 62 | var wid = 0; 63 | var hei = 0; 64 | for(e in data) 65 | switch e { 66 | case CHeader(h): 67 | wid = h.width; 68 | hei = h.height; 69 | case _: 70 | } 71 | 72 | // Read pixels 73 | for(e in data) 74 | switch e { 75 | case CData(bytes): 76 | var dst = haxe.io.Bytes.alloc(wid*hei*4); 77 | format.png.Tools.extract32(data, dst, false); 78 | 79 | return { 80 | width: wid, 81 | height: hei, 82 | decodedBytes: dst, 83 | }; 84 | 85 | case _: 86 | } 87 | } 88 | catch(e:Dynamic) { 89 | throw "Failed to read PNG, err="+e; 90 | } 91 | 92 | return null; 93 | } 94 | 95 | static function decodeGif(b:haxe.io.Bytes) : Null { 96 | try { 97 | var i = new haxe.io.BytesInput(b); 98 | var reader = new format.gif.Reader(i); 99 | var data = reader.read(); 100 | 101 | return { 102 | width: data.logicalScreenDescriptor.width, 103 | height: data.logicalScreenDescriptor.height, 104 | decodedBytes: format.gif.Tools.extractFullBGRA(data, 0), 105 | } 106 | } 107 | catch(e:Dynamic) { 108 | throw "Failed to read GIF"; 109 | } 110 | 111 | return null; 112 | } 113 | 114 | 115 | static function decodeJpeg(encoded:haxe.io.Bytes) : Null { 116 | 117 | #if hl 118 | 119 | // Read image size 120 | var width = 0; 121 | var height = 0; 122 | var i = new haxe.io.BytesInput(encoded); 123 | i.readUInt16(); // skip header 124 | i.bigEndian = true; 125 | while( width==0 && height==0 ) 126 | try { 127 | switch( i.readUInt16() ) { 128 | case 0xffc0, 0xffc1, 0xffc2 : 129 | var len = i.readUInt16(); 130 | var prec = i.readByte(); 131 | height = i.readUInt16(); 132 | width = i.readUInt16(); 133 | 134 | case _: 135 | var len = i.readUInt16(); 136 | i.read(len-2); // skip Jpeg section 137 | } 138 | } catch(e:Dynamic) {} // EOF? 139 | 140 | // Decode 141 | var decoded = haxe.io.Bytes.alloc(width * height * 4); 142 | if( !hl.Format.decodeJPG( encoded.getData(), encoded.length, decoded.getData(), width, height, width * 4, BGRA, 0 ) ) 143 | return null; 144 | else 145 | return { 146 | width: width, 147 | height: height, 148 | decodedBytes: decoded, 149 | } 150 | 151 | #elseif( js && heaps ) 152 | 153 | var d = hxd.res.NanoJpeg.decode(encoded); 154 | return { 155 | width: d.width, 156 | height: d.height, 157 | decodedBytes: d.pixels, 158 | } 159 | 160 | #else 161 | 162 | return null; 163 | 164 | #end 165 | } 166 | 167 | 168 | /** Thanks to Austin East lib "heaps-aseprite" **/ 169 | #if heaps_aseprite 170 | static function decodeAsepriteMainTexture(encoded:haxe.io.Bytes) : Null { 171 | 172 | try { 173 | // Read Aseprite file 174 | var ase = aseprite.Aseprite.fromBytes(encoded); 175 | 176 | // Extract pixels as BGRA 177 | var pixels = ase.getTexture().capturePixels(); 178 | pixels.convert(BGRA); 179 | 180 | return { 181 | decodedBytes: pixels.bytes, 182 | width: pixels.width, 183 | height: pixels.height, 184 | } 185 | } 186 | catch(e:Dynamic) { 187 | lastError = Std.string(e); 188 | return null; 189 | } 190 | 191 | } 192 | #end 193 | 194 | } -------------------------------------------------------------------------------- /src/dn/MarkerMap.hx: -------------------------------------------------------------------------------- 1 | package dn; 2 | 3 | class MarkerMap { 4 | var wid : Int; 5 | var hei : Int; 6 | var marks : haxe.ds.IntMap< haxe.ds.IntMap > = new haxe.ds.IntMap(); 7 | 8 | 9 | public function new(wid:Int, hei:Int) { 10 | this.wid = wid; 11 | this.hei = hei; 12 | } 13 | 14 | 15 | public function dispose() { 16 | marks = null; 17 | } 18 | 19 | 20 | /** TRUE if given coords are in level bounds **/ 21 | inline function isValid(cx,cy) return cx>=0 && cx=0 && cy) { 66 | for(m in marks.keyValueIterator()) 67 | if( m.value.exists( coordId(cx,cy) ) ) { 68 | if( !targetMarks.marks.exists(m.key) ) 69 | targetMarks.marks.set(m.key, new haxe.ds.IntMap()); 70 | targetMarks.marks.get(m.key).set( coordId(cx,cy), m.value.get(coordId(cx,cy)) ); 71 | } 72 | } 73 | 74 | 75 | /** Add a mark + a specific bit at coordinates **/ 76 | public inline function setWithBit(mark:T, subBit:Int, cx:Int, cy:Int, clearExistingBits=false) { 77 | if( isValid(cx,cy) && !hasWithBit(mark, subBit, cx,cy) ) { 78 | if( !marks.exists(mark) ) 79 | marks.set(mark, new haxe.ds.IntMap()); 80 | 81 | var markMap = marks.get(mark); 82 | if( clearExistingBits || !markMap.exists( coordId(cx,cy) ) ) 83 | markMap.set( coordId(cx,cy), M.setBit(0,subBit) ); 84 | else 85 | markMap.set( coordId(cx,cy), M.setBit(markMap.get(coordId(cx,cy)), subBit) ); 86 | } 87 | } 88 | 89 | /** Add a mark + a list of specified bits at coordinates **/ 90 | public inline function setWithBits(mark:T, subBits:Array, cx:Int, cy:Int, clearExistingBits=false) { 91 | for(bit in subBits) 92 | setWithBit(mark, bit, cx,cy, clearExistingBits); 93 | } 94 | 95 | 96 | /** Remove all marks at coordinates **/ 97 | public inline function clearAllAt(cx:Int, cy:Int) { 98 | for(m in marks) 99 | m.remove( coordId(cx,cy) ); 100 | } 101 | 102 | 103 | /** Remove a mark at coordinates **/ 104 | public inline function clearMarkAt(mark:T, cx:Int, cy:Int) { 105 | if( isValid(cx,cy) && has(mark, cx,cy) ) 106 | marks.get(mark).remove( coordId(cx,cy) ); 107 | } 108 | 109 | 110 | /** Remove a specific bit from a mark at coordinates **/ 111 | public inline function clearBitAt(mark:T, subBit:Int, cx:Int, cy:Int) { 112 | if( isValid(cx,cy) && hasWithBit(mark, subBit, cx,cy) ) 113 | marks.get(mark).set( 114 | coordId(cx,cy), 115 | M.unsetBit( marks.get(mark).get(coordId(cx,cy)), subBit ) 116 | ); 117 | } 118 | 119 | } -------------------------------------------------------------------------------- /src/dn/Rand.hx: -------------------------------------------------------------------------------- 1 | package dn; 2 | 3 | // Parker-Miller-Carta LCG 4 | 5 | /* 6 | Known issues : 7 | - don't use negative seeds 8 | - first random(7) is 0 or 6 for small seeds 9 | and some correlated random results can appear for 10 | multiples of 7 11 | */ 12 | 13 | class Rand { 14 | 15 | #if old_f9rand 16 | var seed : UInt; 17 | #else 18 | var seed : #if (flash9 || cpp || hl || neko) Float #else Int #end; 19 | #end 20 | 21 | public function new( seed:Int, callInitSeed=false ) { 22 | this.seed = ((seed < 0) ? -seed : seed) + 131; 23 | if( callInitSeed ) 24 | initSeed(seed); 25 | } 26 | 27 | public inline function clone() { 28 | var r = new Rand(0); 29 | r.seed = seed; 30 | return r; 31 | } 32 | 33 | /** 34 | * @return an int within the 30 bit int range 35 | * int bits are not fully distributed, so it works well with small modulo 36 | */ 37 | public inline function random( n ) : Int { 38 | 39 | #if neko 40 | return if( n == 0 ) int() * 0 else int() % n; 41 | #elseif flash 42 | return int() % n; 43 | #else 44 | /* #if debug 45 | if (n == 0) 46 | throw "WARNING : random(0) is invalid for non neko/flash targets" ; 47 | #end */ 48 | return int() % n; 49 | #end 50 | } 51 | 52 | /** Randomly variate given value `v` in +/- `pct`%. If `sign` is true, the value will some times be multiplied by -1. **/ 53 | public inline function around(v:Float, pct=10, sign=false) { 54 | return v * ( 1 + range(0,pct/100,true) ) * ( sign ? this.sign() : 1 ); 55 | } 56 | 57 | 58 | public inline function range(min:Float, max:Float,?randSign=false) { // tirage inclusif [min, max] 59 | return ( min + rand() * (max-min) ) * (randSign ? random(2)*2-1 : 1); 60 | } 61 | 62 | public inline function irange(min:Int, max:Int, ?randSign=false) { // tirage inclusif [min, max] 63 | return ( min + random(max-min+1) ) * (randSign ? random(2)*2-1 : 1); 64 | } 65 | 66 | public function getSeed() { 67 | return Std.int(seed) - 131; 68 | } 69 | 70 | public #if !hl inline #end function arrayPick(a:Array) : Null { 71 | return a.length==0 ? null : a[random(a.length)]; 72 | } 73 | public #if !hl inline #end function arraySplice(a:Array) : Null { 74 | return a.length==0 ? null : a.splice(random(a.length),1)[0]; 75 | } 76 | public #if !hl inline #end function vectorPick(a:haxe.ds.Vector) : Null { 77 | return a.length==0 ? null : a[random(a.length)]; 78 | } 79 | 80 | /** 81 | * @return a [0-1] float with a precision of 1/10007 82 | * @see random() for int retrieval 83 | */ 84 | public inline function rand() : Float{ 85 | // we can't use a divider > 16807 or else two consecutive seeds 86 | // might generate a similar float 87 | return (int() % 10007) / 10007.0; 88 | } 89 | 90 | public inline function sign() { 91 | return random(2)*2-1; 92 | } 93 | 94 | /** Return TRUE if a random percentage (ie. 0-100) is below given threshold **/ 95 | public inline function pcti(percentageChance:Int) { 96 | return random(100) < percentageChance; 97 | } 98 | 99 | /** Return TRUE if a random percentage (ie. 0-1) is below given threshold **/ 100 | public inline function pctf(percentageChance:Float) { 101 | return rand() < percentageChance; 102 | } 103 | 104 | public inline function addSeed( d : Int ) { 105 | #if neko 106 | seed = untyped __dollar__int((seed + d) % 2147483647.0) & 0x3FFFFFFF; 107 | #elseif (flash9 || cpp || hl) 108 | seed = Std.int((seed + d) % 2147483647.0) & 0x3FFFFFFF; 109 | #else 110 | seed = ((seed + d) % 0x7FFFFFFF) & 0x3FFFFFFF; 111 | #end 112 | if( seed == 0 ) seed = d + 1; 113 | } 114 | 115 | public function initSeed( n : Int, ?k = 5 ) { 116 | // we are using a double hashing function 117 | // that we loop K times. It seems to provide 118 | // good-enough randomness. In case it doesn't, 119 | // we can use an higher K 120 | for( i in 0...k ) { 121 | n ^= (n << 7) & 0x2b5b2500; 122 | n ^= (n << 15) & 0x1b8b0000; 123 | n ^= n >>> 16; 124 | n &= 0x3FFFFFFF; 125 | var h = 5381; 126 | h = (h << 5) + h + (n & 0xFF); 127 | h = (h << 5) + h + ((n >> 8) & 0xFF); 128 | h = (h << 5) + h + ((n >> 16) & 0xFF); 129 | h = (h << 5) + h + (n >> 24); 130 | n = h & 0x3FFFFFFF; 131 | } 132 | seed = (n & 0x1FFFFFFF) + 131; 133 | } 134 | 135 | /** 136 | * int bits are not fully distributed, so it works well with small modulo 137 | * @return an int 138 | */ 139 | inline function int() : Int { 140 | #if neko 141 | return untyped __dollar__int( seed = (seed * 16807.0) % 2147483647.0 ) & 0x3FFFFFFF; 142 | #elseif (flash9 || cpp || hl) 143 | #if old_f9rand 144 | return seed = Std.int((seed * 16807.0) % 2147483647.0) & 0x3FFFFFFF; 145 | #else 146 | return Std.int(seed = (seed * 16807.0) % 2147483647.0) & 0x3FFFFFFF; 147 | #end 148 | #else 149 | return (seed = (seed * 16807) % 0x7FFFFFFF) & 0x3FFFFFFF; 150 | #end 151 | } 152 | 153 | 154 | 155 | #if deepnightLibsTests 156 | public static function test() { 157 | var random = new Rand(0); 158 | for(i in 0...20) { 159 | var seed = Std.random(999999); 160 | random.initSeed(seed); 161 | var a = random.rand(); 162 | random.initSeed(seed); 163 | CiAssert.printIfVerbose("Seed="+seed+":"); 164 | CiAssert.equals( random.rand(), a ); 165 | } 166 | } 167 | #end 168 | 169 | } 170 | -------------------------------------------------------------------------------- /src/dn/TinyTween.hx: -------------------------------------------------------------------------------- 1 | package dn; 2 | 3 | enum abstract TinyTweenInterpolation(Int) to Int { 4 | var Linear; 5 | var EaseIn; 6 | var EaseOut; 7 | var EaseInOut; 8 | var BackForth; 9 | } 10 | 11 | class TinyTween { 12 | var fps : Int; 13 | var fromValue : Float; 14 | var toValue : Float; 15 | var elapsedS : Float; 16 | var durationS : Float; 17 | var interp : TinyTweenInterpolation = EaseInOut; 18 | public var curValue(get,never) : Float; 19 | 20 | public inline function new(fps) { 21 | this.fps = fps; 22 | reset(); 23 | } 24 | 25 | /** Reset the tween completely **/ 26 | public inline function reset() { 27 | fromValue = toValue = 0; 28 | durationS = elapsedS = -1; 29 | } 30 | 31 | /** Return TRUE if the tween has any valid value (ie. it's either started, or completed). Reseting makes this function FALSE. **/ 32 | public inline function hasAnyValue() { 33 | return durationS>0; 34 | } 35 | 36 | inline function get_curValue() { 37 | if( !hasAnyValue() ) 38 | return 0; 39 | 40 | if( isComplete() ) 41 | return switch interp { 42 | case BackForth: fromValue; 43 | case _: toValue; 44 | } 45 | 46 | final ratio = switch interp { 47 | case Linear: elapsedS/durationS; 48 | case EaseIn: M.bezier4( elapsedS/durationS, 0, 0, 0.5, 1 ); 49 | case EaseOut: M.bezier4( elapsedS/durationS, 0, 0.5, 1, 1 ); 50 | case EaseInOut: M.bezierFull4( elapsedS/durationS, 0, 0.66, 0.66, 1, 0, 0, 1, 1 ); 51 | case BackForth: M.bezier4( elapsedS/durationS, 0, 1+1/3, 1+1/3, 0 ); 52 | } 53 | return fromValue + ( toValue - fromValue ) * ratio; 54 | } 55 | 56 | public inline function isComplete() { 57 | return hasAnyValue() && elapsedS>=durationS; 58 | } 59 | 60 | public inline function isCurrentlyRunning() { 61 | return hasAnyValue() && elapsedS):Array; 26 | abstract public function unlock(ach:Achievements):Bool; 27 | 28 | abstract function internalClear(ach:Achievements):Void; 29 | function getAchById(id:String,achs:cdb.Types.ArrayRead):Achievements{ 30 | for (ach in achs){ 31 | if(ach.Id.toString() == id) return ach; 32 | } 33 | return null; 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /src/dn/achievements/AchievementsManager.hx: -------------------------------------------------------------------------------- 1 | package dn.achievements; 2 | 3 | import dn.achievements.AbstractAchievementPlatform; 4 | import dn.data.AchievementDb; 5 | 6 | 7 | class AchievementsManager { 8 | public var isLocal(get,never):Bool; inline function get_isLocal() return platform.isLocal; 9 | 10 | var dones : Array; 11 | var platform:AbstractAchievementPlatform; 12 | 13 | public function new(platform:AbstractAchievementPlatform,?dbName:String = "achievements.cdb") { 14 | this.platform = platform; 15 | 16 | AchievementDb.load(hxd.Res.load(dbName).toText()); 17 | this.platform.init(); 18 | dones = this.platform.getUnlocked(AchievementDb.achievements.all); 19 | } 20 | 21 | public function clear(id:String) { 22 | dones.remove(id); 23 | platform.clear(getAchievementByName(id)); 24 | } 25 | 26 | public function getAllIds() { 27 | var ids = []; 28 | for(ach in AchievementDb.achievements.all) 29 | ids.push(ach.Id.toString()); 30 | return ids; 31 | } 32 | 33 | public function complete(id:String) { 34 | if( !isCompleted(id) ) { 35 | platform.unlock(getAchievementByName(id)); 36 | dones.push(id); 37 | return true; 38 | } 39 | else 40 | return false; 41 | } 42 | 43 | public inline function isCompleted(id:String) { 44 | return hasAchievement(id) && dones.contains(id); 45 | } 46 | 47 | public inline function hasAchievement(id:String):Bool { 48 | return getAchievementByName(id) != null; 49 | } 50 | 51 | public function getAchievementByName(name:String):Achievements{ 52 | return AchievementDb.achievements.resolve(name); 53 | } 54 | } -------------------------------------------------------------------------------- /src/dn/achievements/LocalAchivementPlatform.hx: -------------------------------------------------------------------------------- 1 | package dn.achievements; 2 | 3 | import haxe.ds.StringMap; 4 | import dn.data.AchievementDb; 5 | 6 | /** 7 | * Local Achivement platform 8 | * Save in Array 9 | */ 10 | class LocalAchivementPlatform extends AbstractAchievementPlatform { 11 | var dones : Array; 12 | 13 | public function new() { 14 | super(); 15 | isLocal = true; 16 | } 17 | 18 | public function updateDones(originalStatus:Array = null) { 19 | if(originalStatus == null) originalStatus = []; 20 | dones = originalStatus; 21 | } 22 | public function init() {} 23 | 24 | function internalClear(ach:Achievements) { 25 | var ok = dones.remove(ach.Id.toString()); 26 | trace(ach.Id+": "+(ok?"Ok":"FAILED!")); 27 | } 28 | 29 | public function getUnlocked(?achs:cdb.Types.ArrayRead):Array{ 30 | return dones; 31 | } 32 | 33 | public function unlock(ach:Achievements):Bool { 34 | if(!dones.contains(ach.Id.toString()))dones.push(ach.Id.toString()); 35 | return true; 36 | } 37 | } -------------------------------------------------------------------------------- /src/dn/achievements/SteamAchivementPlatform.hx: -------------------------------------------------------------------------------- 1 | package dn.achievements; 2 | 3 | import cdb.Types.ArrayRead; 4 | #if hlsteam 5 | import dn.data.AchievementDb; 6 | 7 | /** 8 | * Steam platform support 9 | */ 10 | class SteamAchivementPlatform extends AbstractAchievementPlatform { 11 | 12 | 13 | public function init() {} 14 | 15 | function internalClear(ach:Achievements) { 16 | var ok = steam.Api.clearAchievement(ach.steamID); 17 | trace(ach.Id+": "+(ok?"Ok":"FAILED!")); 18 | } 19 | 20 | public function getUnlocked(?achs:cdb.Types.ArrayRead):Array{ 21 | var dones = new Array(); 22 | for(idx in 0...steam.Api.getNumAchievements()) { 23 | var id = steam.Api.getAchievementAPIName(idx); 24 | if( steam.Api.getAchievement(id) ) 25 | dones.push(getAchById(id,achs).Id.toString()); 26 | } 27 | return dones; 28 | } 29 | 30 | public function unlock(ach:Achievements):Bool { 31 | return steam.Api.setAchievement(ach.steamID); 32 | } 33 | 34 | override function getAchById(id:String, achs:ArrayRead):Achievements { 35 | for (ach in achs){ 36 | if(ach.steamID == id) return ach; 37 | } 38 | return null; 39 | } 40 | } 41 | #end -------------------------------------------------------------------------------- /src/dn/data/AchievementDb.hx: -------------------------------------------------------------------------------- 1 | package dn.data; 2 | 3 | private typedef Init = haxe.macro.MacroType<[cdb.Module.build("achievements.cdb")]>; -------------------------------------------------------------------------------- /src/dn/debug/MemTrack.hx: -------------------------------------------------------------------------------- 1 | package dn.debug; 2 | 3 | #if macro 4 | import haxe.macro.Expr; 5 | import haxe.macro.Context; 6 | #end 7 | 8 | 9 | class MemAlloc { 10 | public var total = 0.; 11 | public var calls = 0; 12 | public inline function new() {} 13 | } 14 | 15 | class MemTrack { 16 | @:noCompletion 17 | public static var allocs : Map = new Map(); 18 | 19 | @:noCompletion 20 | public static var firstMeasure = -1.; 21 | 22 | /** Measure a block or a function call memory usage **/ 23 | public static macro function measure( e:Expr, ?ename:ExprOf ) { 24 | #if !debug 25 | 26 | return e; 27 | 28 | #else 29 | 30 | var p = Context.getPosInfos( Context.currentPos() ); 31 | var id = Context.getLocalModule()+"."+Context.getLocalMethod()+"@"+p.min+": "; 32 | id += switch e.expr { 33 | case ECall(e, params): 34 | haxe.macro.ExprTools.toString(e)+"()"; 35 | 36 | case EBlock(_): 37 | ""; 38 | 39 | case _: 40 | '<${e.expr.getName()}>'; 41 | } 42 | 43 | switch ename.expr { 44 | case EConst(CIdent("null")): 45 | case _: id+="."; 46 | } 47 | 48 | return macro { 49 | if( dn.debug.MemTrack.firstMeasure<0 ) 50 | dn.debug.MemTrack.firstMeasure = haxe.Timer.stamp(); 51 | var old = dn.Gc.getCurrentMem(); 52 | 53 | $e; 54 | 55 | var m = dn.M.fmax( 0, dn.Gc.getCurrentMem() - old ); 56 | 57 | var id = $v{id}; 58 | if( $ename!=null ) 59 | id+=$ename; 60 | if( !dn.debug.MemTrack.allocs.exists(id) ) 61 | @:privateAccess dn.debug.MemTrack.allocs.set(id, new dn.debug.MemTrack.MemAlloc()); 62 | var alloc = dn.debug.MemTrack.allocs.get(id); 63 | alloc.total += m; 64 | alloc.calls++; 65 | } 66 | 67 | #end 68 | } 69 | 70 | /** Reset current allocs tracking **/ 71 | public static function reset() { 72 | allocs = new Map(); 73 | firstMeasure = -1; 74 | } 75 | 76 | 77 | 78 | static inline function padRight(str:String, minLen:Int, padChar=" ") { 79 | while( str.lengthVoid, alsoReset=true) { 89 | var t = haxe.Timer.stamp() - firstMeasure; 90 | 91 | if( printer==null ) 92 | printer = (v)->trace(v); 93 | 94 | var all = []; 95 | for(a in allocs.keyValueIterator()) 96 | all.push({id: a.key, mem:a.value }); 97 | all.sort( (a,b) -> -Reflect.compare(a.mem.total/t, b.mem.total/t) ); 98 | 99 | if( all.length==0 ) { 100 | printer("MemTrack has nothing to report."); 101 | return; 102 | } 103 | 104 | printer("MEMTRACK REPORT"); 105 | printer('Elapsed time: ${M.pretty(t,1)}s'); 106 | var table = [["", "MEM/S", "TOTAL"]]; 107 | var total = 0.; 108 | for(a in all) { 109 | total+=a.mem.total; 110 | table.push([ 111 | a.id, 112 | dn.M.unit(a.mem.total/t)+"/s", 113 | dn.M.unit(a.mem.total), 114 | ]); 115 | } 116 | 117 | // Build visual table 118 | var colWidths : Array = []; 119 | for(line in table) { 120 | for(i in 0...line.length) 121 | if( !M.isValidNumber(colWidths[i]) ) 122 | colWidths[i] = line[i].length; 123 | else 124 | colWidths[i] = M.imax(colWidths[i], line[i].length); 125 | } 126 | // Create header line 127 | inline function _separator() { 128 | var line = []; 129 | for(i in 0...colWidths.length) 130 | line.push( padRight("", colWidths[i],"-") ); 131 | return line; 132 | } 133 | table.insert( 1, _separator() ); 134 | table.push(_separator()); 135 | table.push(["", dn.M.unit(total/t)+"/s", dn.M.unit(total)]); 136 | 137 | // Print table 138 | for(line in table) { 139 | for(i in 0...line.length) 140 | line[i] = padRight(line[i], colWidths[i]); 141 | printer("| " + line.join(" | ") + " |"); 142 | } 143 | 144 | if( alsoReset ) 145 | reset(); 146 | } 147 | } -------------------------------------------------------------------------------- /src/dn/geom/GridPoint.hx: -------------------------------------------------------------------------------- 1 | package dn.geom; 2 | 3 | class GridPoint { 4 | public var x : Int; 5 | public var y : Int; 6 | 7 | /** cx alias **/ 8 | public var cx(get,set) : Int; 9 | inline function get_cx() return x; 10 | inline function set_cx(v:Int) return x = v; 11 | 12 | /** cy alias **/ 13 | public var cy(get,set) : Int; 14 | inline function get_cy() return y; 15 | inline function set_cy(v:Int) return y = v; 16 | 17 | 18 | public inline function new(x,y) { 19 | this.x = x; 20 | this.y = y; 21 | } 22 | 23 | @:keep public function toString() return '<$x,$y>'; 24 | } -------------------------------------------------------------------------------- /src/dn/heaps/Boundless.hx: -------------------------------------------------------------------------------- 1 | package dn.heaps; 2 | 3 | /** 4 | The Bounds of this utility class are always empty, even if it has children. 5 | This is useful with h2d.Flow, to add a child that doesn't affect the Flow bounds. 6 | **/ 7 | class Boundless extends h2d.Object { 8 | public function new(setAbsoluteInParentFlow=false, ?parent) { 9 | super(parent); 10 | if( setAbsoluteInParentFlow && Std.isOfType(parent,h2d.Flow) ) 11 | Std.downcast(parent, h2d.Flow).getProperties(this).isAbsolute = true; 12 | } 13 | 14 | override function getBoundsRec(relativeTo:h2d.Object, out:h2d.col.Bounds, forSize:Bool) { 15 | super.getBoundsRec(relativeTo, out, forSize); 16 | out.empty(); 17 | } 18 | } -------------------------------------------------------------------------------- /src/dn/heaps/FlowBg.hx: -------------------------------------------------------------------------------- 1 | package dn.heaps; 2 | 3 | class FlowBg extends h2d.Flow { 4 | var _bg : Null; 5 | 6 | var bgTile : Null; 7 | public var bgBorderLeft : Int; 8 | public var bgBorderRight : Int; 9 | public var bgBorderTop : Int; 10 | public var bgBorderBottom : Int; 11 | var bgColor : Col = 0; 12 | 13 | public var repeatBorders = true; 14 | public var repeatCenter = true; 15 | 16 | 17 | public function new(?bgTile:h2d.Tile, horizontalBorder=1, ?verticalBorder:Int, ?p:h2d.Object) { 18 | super(p); 19 | setBg(bgTile, horizontalBorder, verticalBorder); 20 | } 21 | 22 | public inline function setBg(t:h2d.Tile, horizontalBorder:Int, ?verticalBorder:Int) { 23 | bgTile = t; 24 | setBgBorder(horizontalBorder, verticalBorder); 25 | } 26 | 27 | public inline function setBgBorder(horizontal:Int, ?vertical:Int) { 28 | bgBorderLeft = bgBorderRight = horizontal; 29 | bgBorderTop = bgBorderBottom = vertical ?? horizontal; 30 | } 31 | 32 | public inline function colorizeBg(c:Col, alpha=1.0) { 33 | bgColor = c.withAlpha(alpha); 34 | } 35 | 36 | override function sync(ctx:h2d.RenderContext) { 37 | super.sync(ctx); 38 | 39 | if( bgTile!=null ) { 40 | // Recreate bg if needed 41 | if( _bg==null || _bg.parent==null ) { 42 | _bg = new h2d.ScaleGrid(bgTile, 1,1); 43 | addChildAt(_bg, 0); 44 | getProperties(_bg).isAbsolute = true; 45 | } 46 | 47 | _bg.tile = bgTile; 48 | _bg.borderLeft = bgBorderLeft; 49 | _bg.borderRight = bgBorderRight; 50 | _bg.borderTop = bgBorderTop; 51 | _bg.borderBottom = bgBorderBottom; 52 | if( bgColor!=0 ) 53 | _bg.color.setColor( bgColor.withAlphaIfMissing() ); 54 | _bg.width = outerWidth; 55 | _bg.height = outerHeight; 56 | _bg.tileBorders = repeatBorders; 57 | _bg.tileCenter = repeatCenter; 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /src/dn/heaps/GameFocusHelper.hx: -------------------------------------------------------------------------------- 1 | package dn.heaps; 2 | 3 | class GameFocusHelper extends dn.Process { 4 | var suspended = false; 5 | var mask : h2d.Object; 6 | var font : h2d.Font; 7 | var scene : h2d.Scene; 8 | var showIntro = false; 9 | var thumb : h2d.Tile; 10 | 11 | #if js 12 | var jsFocus = false; 13 | #end 14 | 15 | public function new(s:h2d.Scene, font:h2d.Font, ?thumb:h2d.Tile) { 16 | super(); 17 | 18 | this.thumb = thumb; 19 | this.font = font; 20 | this.scene = s; 21 | createRoot(scene); 22 | root.visible = false; 23 | 24 | #if (js && !nodejs) 25 | showIntro = true; 26 | suspendGame(); 27 | var doc = js.Browser.document; 28 | function _checkTouch(ev:js.html.Event) { 29 | var jsCanvas = @:privateAccess hxd.Window.getInstance().canvas; 30 | var te : js.html.Element = cast ev.target; 31 | jsFocus = jsCanvas.isSameNode(te); 32 | } 33 | doc.addEventListener("touchstart", _checkTouch); 34 | doc.addEventListener("click", _checkTouch); 35 | #else 36 | if( !isFocused() ) { 37 | showIntro = true; 38 | suspendGame(); 39 | } 40 | #end 41 | 42 | // #if js 43 | // @:privateAccess hxd.snd.NativeChannel.stopInput(null); 44 | // #end 45 | } 46 | 47 | static function isMobile() { 48 | #if js 49 | var mobileReg = ~/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/gi; 50 | return mobileReg.match( js.Browser.navigator.userAgent ); 51 | #else 52 | return false; 53 | #end 54 | } 55 | 56 | public static function isUseful() { 57 | #if js 58 | return !isMobile(); 59 | #else 60 | return switch hxd.System.platform { 61 | case WebGL: !isMobile(); 62 | case IOS, Android: false; 63 | case PC: false; 64 | case Console: false; 65 | case FlashPlayer: true; 66 | } 67 | #end 68 | } 69 | 70 | function suspendGame() { 71 | if( suspended ) 72 | return; 73 | 74 | suspended = true; 75 | dn.heaps.slib.SpriteLib.DISABLE_ANIM_UPDATES = true; 76 | 77 | // Pause other process 78 | for(p in Process.ROOTS) 79 | if( p!=this ) 80 | p.pause(); 81 | 82 | // Create mask 83 | root.visible = true; 84 | root.removeChildren(); 85 | 86 | var isThumb = showIntro && thumb!=null; 87 | var t = showIntro && thumb==null 88 | ? h2d.Tile.fromColor(0x252e43, 1,1, 1) 89 | : showIntro && thumb!=null 90 | ? thumb 91 | : h2d.Tile.fromColor(0x0, 1,1, 0.6); 92 | var bg = new h2d.Bitmap(t, root); 93 | var i = new h2d.Interactive(1,1, root); 94 | 95 | var tf = new h2d.Text(font, root); 96 | if( showIntro ) 97 | tf.text = "Click anywhere to start"; 98 | else 99 | tf.text = "PAUSED - click anywhere to resume"; 100 | 101 | createChildProcess( 102 | function(c) { 103 | // Resize dynamically 104 | tf.setScale( M.imax(1, Math.floor( stageWid*0.5 / tf.textWidth )) ); 105 | tf.x = Std.int( stageWid*0.5 - tf.textWidth*tf.scaleX*0.5 ); 106 | tf.y = Std.int( stageHei*0.5 - tf.textHeight*tf.scaleY*0.5 ); 107 | 108 | i.width = stageWid+1; 109 | i.height = stageHei+1; 110 | if( isThumb ) { 111 | var s = M.fmax( stageWid/thumb.width, stageHei/thumb.height ); 112 | bg.filter = new h2d.filter.Blur(32,1,3); 113 | bg.setScale(s); 114 | bg.x = stageWid*0.5 - bg.tile.width*bg.scaleX*0.5; 115 | bg.y = stageHei*0.5 - bg.tile.height*bg.scaleY*0.5; 116 | } 117 | else { 118 | bg.scaleX = stageWid+1; 119 | bg.scaleY = stageHei+1; 120 | } 121 | 122 | // Auto-kill 123 | if( !suspended ) 124 | c.destroy(); 125 | }, true 126 | ); 127 | 128 | var loadingMsg = showIntro; 129 | i.onPush = function(_) { 130 | if( loadingMsg ) { 131 | tf.text = "Loading, please wait..."; 132 | tf.x = Std.int( stageWid*0.5 - tf.textWidth*tf.scaleX*0.5 ); 133 | tf.y = Std.int( stageHei*0.5 - tf.textHeight*tf.scaleY*0.5 ); 134 | delayer.addS(resumeGame, 1); 135 | } 136 | else 137 | resumeGame(); 138 | i.remove(); 139 | } 140 | 141 | showIntro = false; 142 | } 143 | 144 | function resumeGame() { 145 | if( !suspended ) 146 | return; 147 | dn.heaps.slib.SpriteLib.DISABLE_ANIM_UPDATES = false; 148 | 149 | delayer.addF(function() { 150 | root.visible = false; 151 | root.removeChildren(); 152 | }, 1); 153 | suspended = false; 154 | 155 | for(p in Process.ROOTS) 156 | if( p!=this ) 157 | p.resume(); 158 | } 159 | 160 | inline function isFocused() { 161 | #if (flash || nodejs) 162 | return true; 163 | #elseif js 164 | return jsFocus; 165 | #else 166 | var w = hxd.Window.getInstance(); 167 | return w.isFocused; 168 | #end 169 | } 170 | 171 | override function update() { 172 | super.update(); 173 | 174 | if( suspended ) 175 | scene.over(root); 176 | 177 | if( !cd.hasSetS("check",0.2) ) { 178 | if( !isFocused() && !suspended ) 179 | suspendGame(); 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/dn/heaps/Palette.hx: -------------------------------------------------------------------------------- 1 | package dn.heaps; 2 | 3 | class Palette { 4 | var colors : Array = []; 5 | var _cachedClosests : Map = new Map(); 6 | 7 | public var length(get,never) : Int; inline function get_length() return colors.length; 8 | 9 | public var black(get,never): Col; inline function get_black() return getClosest("#0"); 10 | public var darkGray(get,never): Col; inline function get_darkGray() return getClosest("#5"); 11 | public var midGray(get,never): Col; inline function get_midGray() return getClosest("#8"); 12 | public var lightGray(get,never): Col; inline function get_lightGray() return getClosest("#c"); 13 | public var white(get,never): Col; inline function get_white() return getClosest("#f"); 14 | 15 | public var yellow(get,never): Col; inline function get_yellow() return getClosest("#ff0"); 16 | public var lightRed(get,never): Col; inline function get_lightRed() return getClosest("#f00"); 17 | public var lightGreen(get,never): Col; inline function get_lightGreen() return getClosest("#0f0"); 18 | public var lightBlue(get,never): Col; inline function get_lightBlue() return getClosest("#00f"); 19 | public var lightOrange(get,never): Col; inline function get_lightOrange() return getClosest("#fb0"); 20 | public var lightCyan(get,never): Col; inline function get_lightCyan() return getClosest("#0ff"); 21 | public var lightPink(get,never): Col; inline function get_lightPink() return getClosest("#f0f"); 22 | 23 | public var darkRed(get,never): Col; inline function get_darkRed() return getClosest("#800"); 24 | public var darkGreen(get,never): Col; inline function get_darkGreen() return getClosest("#080"); 25 | public var darkBlue(get,never): Col; inline function get_darkBlue() return getClosest("#008"); 26 | public var darkOrange(get,never): Col; inline function get_darkOrange() return getClosest("#a80"); 27 | public var darkCyan(get,never): Col; inline function get_darkCyan() return getClosest("#088"); 28 | public var darkPink(get,never): Col; inline function get_darkPink() return getClosest("#808"); 29 | 30 | public inline function new() {} 31 | 32 | public inline function iterator() return colors.iterator(); 33 | 34 | /** 35 | Read a palette from an image pixels 36 | **/ 37 | public static function fromPixels(pixels:hxd.Pixels, eachColorSizePx=1, allowDuplicates=false) : Palette { 38 | var pal = new Palette(); 39 | var x = 0; 40 | var y = 0; 41 | var knowns = new Map(); 42 | while( y=pixels.width ) { 51 | x = 0; 52 | y+=eachColorSizePx; 53 | } 54 | } 55 | return pal; 56 | } 57 | 58 | /** Read palette from an Aseprite image **/ 59 | #if heaps_aseprite 60 | public static function fromAseprite(ase:aseprite.Aseprite) : Palette { 61 | var pal = new Palette(); 62 | for(i in ase.palette.firstColorIndex...ase.palette.lastColorIndex+1) { 63 | var c : Col = ase.palette.entries.get(i); 64 | pal.addColor( c.withoutAlpha() ); 65 | } 66 | return pal; 67 | } 68 | #end 69 | 70 | var cacheIsEmpty = true; 71 | inline function clearCache() { 72 | if( !cacheIsEmpty ) { 73 | cacheIsEmpty = true; 74 | _cachedClosests = new Map(); 75 | } 76 | } 77 | 78 | public inline function addColor(c:Col) { 79 | colors.push(c); 80 | clearCache(); 81 | } 82 | 83 | public inline function removeColor(c:Col) { 84 | colors.remove(c); 85 | clearCache(); 86 | } 87 | 88 | public inline function empty() { 89 | colors = []; 90 | clearCache(); 91 | } 92 | 93 | @:keep 94 | public inline function toString() { 95 | return 'Palette(${colors.length}): ${colors.map(c->c.toString()).join(",")}'; 96 | } 97 | 98 | /** Render current palette colors **/ 99 | public function render(colSizePx=8, ?parent:h2d.Object) : h2d.Flow { 100 | var f = new h2d.Flow(parent); 101 | f.layout = Horizontal; 102 | f.verticalAlign = Middle; 103 | 104 | inline function _renderColor(c:Col, f:h2d.Flow) { 105 | var bmp = new h2d.Bitmap( h2d.Tile.fromColor(c), f ); 106 | bmp.setScale(colSizePx); 107 | } 108 | 109 | for( c in colors ) 110 | _renderColor(c, f); 111 | return f; 112 | } 113 | 114 | /** Render palette preset colors **/ 115 | public function renderPresets(colSizePx=8, ?parent:h2d.Object) : h2d.Flow { 116 | var f = new h2d.Flow(parent); 117 | f.layout = Horizontal; 118 | f.verticalAlign = Top; 119 | 120 | inline function _renderColorPair(c1:Col, c2:Col, f:h2d.Flow) { 121 | var pair = new h2d.Flow(f); 122 | pair.layout = Vertical; 123 | var bmp = new h2d.Bitmap( h2d.Tile.fromColor(c1), pair ); 124 | bmp.setScale(colSizePx); 125 | var bmp = new h2d.Bitmap( h2d.Tile.fromColor(c2), pair ); 126 | bmp.setScale(colSizePx); 127 | } 128 | _renderColorPair("#f", white, f); 129 | _renderColorPair("#c", lightGray, f); 130 | _renderColorPair("#9", midGray, f); 131 | _renderColorPair("#5", darkGray, f); 132 | _renderColorPair("#0", black, f); 133 | 134 | _renderColorPair("#f00", lightRed, f); 135 | _renderColorPair("#0f0", lightGreen, f); 136 | _renderColorPair("#00f", lightBlue, f); 137 | _renderColorPair("#0ff", lightCyan, f); 138 | _renderColorPair("#f0f", lightPink, f); 139 | _renderColorPair("#fa0", lightOrange, f); 140 | _renderColorPair("#ff0", yellow, f); 141 | 142 | _renderColorPair("#800", darkRed, f); 143 | _renderColorPair("#080", darkGreen, f); 144 | _renderColorPair("#008", darkBlue, f); 145 | _renderColorPair("#088", darkCyan, f); 146 | _renderColorPair("#808", darkPink, f); 147 | _renderColorPair("#a80", darkOrange, f); 148 | 149 | return f; 150 | } 151 | 152 | 153 | public inline function getClosest(target:Col, useLab=false) : Col { 154 | if( _cachedClosests.exists(target) ) 155 | return _cachedClosests.get(target); 156 | else { 157 | // Search closest palette entry 158 | var best : Col = 0x0; 159 | var bestDist = 99.; 160 | var dist = 0.; 161 | for(c in colors) { 162 | dist = useLab ? target.getDistanceLab(c) : target.getDistanceRgb(c); 163 | if( dist=1 || reversed && linearRatio<=0 ) { 202 | render(); 203 | complete(); 204 | } 205 | } 206 | } -------------------------------------------------------------------------------- /src/dn/heaps/Skewable.hx: -------------------------------------------------------------------------------- 1 | package dn.heaps; 2 | 3 | class Skewable extends h2d.Object { 4 | public var skewBottom(never,set) : Float; inline function set_skewBottom(v:Float) return skewX = v; 5 | public var skewRight(never,set) : Float; inline function set_skewRight(v:Float) return skewY = v; 6 | 7 | var skewX = 0.; 8 | var skewY = 0.; 9 | 10 | public function new(?p) { 11 | super(p); 12 | } 13 | 14 | public function skewPx(xPixels:Float, yPixels:Float, thisWid:Float, thisHei:Float) { 15 | skewBottom = Math.atan(xPixels/thisHei); 16 | skewRight = Math.atan(yPixels/thisWid); 17 | } 18 | 19 | override function calcAbsPos() { 20 | super.calcAbsPos(); 21 | 22 | // Does not support rotation(s) 23 | if( skewX!=0 || skewY!=0 ) { 24 | matB += matA * Math.tan(skewY); 25 | matC += matD * Math.tan(skewX); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/dn/heaps/TiledTexture.hx: -------------------------------------------------------------------------------- 1 | package dn.heaps; 2 | 3 | class TiledTexture extends h2d.TileGroup { 4 | var invalidated = true; 5 | public var width(default,set) : Int; 6 | public var height(default,set) : Int; 7 | 8 | public var alignPivotX(default,set) = 0.; 9 | public var alignPivotY(default,set) = 0.; 10 | 11 | public var subTilePivotX(default,set) = 0.; 12 | public var subTilePivotY(default,set) = 0.; 13 | 14 | public function new(wid:Int, hei:Int, ?texTile:h2d.Tile, ?p:h2d.Object) { 15 | super(texTile, p); 16 | width = wid; 17 | height = hei; 18 | } 19 | 20 | inline function set_width(v) { invalidated = true; return width = v; } 21 | inline function set_height(v) { invalidated = true; return height = v; } 22 | inline function set_alignPivotX(v) { invalidated = true; return alignPivotX = M.fclamp(v,0,1); } 23 | inline function set_alignPivotY(v) { invalidated = true; return alignPivotY = M.fclamp(v,0,1); } 24 | inline function set_subTilePivotX(v) { invalidated = true; return subTilePivotX = M.fclamp(v,0,1); } 25 | inline function set_subTilePivotY(v) { invalidated = true; return subTilePivotY = M.fclamp(v,0,1); } 26 | 27 | public inline function resize(wid:Int,hei:Int) { 28 | width = wid; 29 | height = hei; 30 | } 31 | 32 | function build() { 33 | clear(); 34 | if (tile == null) return; 35 | var initialX = width*alignPivotX - tile.width*alignPivotX; 36 | initialX -= M.ceil(initialX/tile.width)*tile.width; 37 | var initialY = height*alignPivotY - tile.height*alignPivotY; 38 | initialY -= M.ceil(initialY/tile.height)*tile.height; 39 | var x = initialX; 40 | var y = initialY; 41 | var ox = M.round( -subTilePivotX*width ); 42 | var oy = M.round( -subTilePivotY*height ); 43 | var w = Std.int( tile.width ); 44 | var h = Std.int( tile.height ); 45 | while( y=width ) { 51 | x = initialX; 52 | y += h; 53 | } 54 | } 55 | } 56 | 57 | override function sync(ctx:h2d.RenderContext) { 58 | if( invalidated ) { 59 | invalidated = false; 60 | build(); 61 | } 62 | super.sync(ctx); 63 | } 64 | 65 | override function drawTo(t:h3d.mat.Texture) { 66 | if (tile == null) return; 67 | 68 | var initialX = width*alignPivotX - tile.width*alignPivotX; 69 | initialX -= M.ceil(initialX/tile.width)*tile.width; 70 | var initialY = height*alignPivotY - tile.height*alignPivotY; 71 | initialY -= M.ceil(initialY/tile.height)*tile.height; 72 | var x = initialX; 73 | var y = initialY; 74 | var ox = M.round( -subTilePivotX*width ); 75 | var oy = M.round( -subTilePivotY*height ); 76 | var w = Std.int( tile.width ); 77 | var h = Std.int( tile.height ); 78 | while( y=width ) { 87 | x = initialX; 88 | y += h; 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/dn/heaps/assets/Atlas.hx: -------------------------------------------------------------------------------- 1 | package dn.heaps.assets; 2 | 3 | import dn.heaps.slib.SpriteLib; 4 | 5 | class Atlas { 6 | static var CACHE_ANIMS : Array> = []; 7 | public static var LOADING_TICK_FUN : Void -> Void; 8 | 9 | static function ltick(){ 10 | if( LOADING_TICK_FUN != null ) 11 | LOADING_TICK_FUN(); 12 | } 13 | 14 | public static function load(atlasPath:String, ?onReload:Void->Void, ?notZeroBaseds:Array, ?properties : Array) : SpriteLib { 15 | var notZeroMap = new Map(); 16 | if( notZeroBaseds!=null ) 17 | for(id in notZeroBaseds) notZeroMap.set(id, true); 18 | 19 | var propertiesMap = new Map(); 20 | if (properties != null) 21 | for (i in 0...properties.length) propertiesMap.set(properties[i], properties.length - 1 - i); 22 | 23 | var res = hxd.Res.load(atlasPath); 24 | var basePath = atlasPath.indexOf("/")<0 ? "" : atlasPath.substr(0, atlasPath.lastIndexOf("/")+1); 25 | 26 | // Load source image separately 27 | /* 28 | var r = ~/^([a-z0-9_\-]+)\.((png)|(jpg)|(jpeg)|(gif))/igm; 29 | var raw = res.toText(); 30 | var bd : hxd.BitmapData = null; 31 | if( r.match(raw) ) { 32 | bd = hxd.Res.load(basePath + r.matched(0)).toBitmap(); 33 | } 34 | */ 35 | 36 | // Create SLib 37 | var atlas = res.to(hxd.res.Atlas); 38 | var lib = convertToSlib(atlas, notZeroMap, propertiesMap, atlasPath); 39 | res.watch( function() { 40 | #if debug 41 | trace("Reloaded atlas."); 42 | #end 43 | convertToSlib(atlas, notZeroMap, propertiesMap, atlasPath); 44 | if( onReload!=null ) 45 | onReload(); 46 | }); 47 | 48 | return lib; 49 | } 50 | 51 | static function convertToSlib(atlas:hxd.res.Atlas, notZeroBaseds:Map, properties : Map, atlasName: String) { 52 | ltick(); 53 | var contents = atlas.getContents(); 54 | ltick(); 55 | 56 | var bestVariants = new Map(); 57 | 58 | var propertiesReg = ~/(.*)((\.[a-z_\-]+)+)$/gi; 59 | for (rawName in contents.keys()) { 60 | var groupName = rawName; 61 | var groupProps = new Array(); 62 | if (propertiesReg.match(rawName)) { 63 | var str = propertiesReg.matched(2).substr(1); 64 | groupProps = str.split("."); 65 | groupName = propertiesReg.matched(1); 66 | } 67 | 68 | var score = 0; 69 | if (groupProps.length > 0) { 70 | for (i in 0...groupProps.length) { 71 | var prio = properties.get(groupProps[i]); 72 | if (prio != null) score |= 1 << prio; 73 | } 74 | if (score == 0) continue; 75 | } 76 | 77 | var e = bestVariants.get(groupName); 78 | if (e == null) { 79 | bestVariants.set(groupName, { rawName : rawName, score : score }); 80 | } else if (score > e.score) { 81 | e.rawName = rawName; 82 | e.score = score; 83 | } 84 | } 85 | 86 | var pageMap = new Map(); 87 | var pages = new Array(); 88 | 89 | for (group in contents) 90 | for (frame in group) { 91 | var tex = frame.t.getTexture(); 92 | var page = pageMap.get(tex); 93 | if (page == null) { 94 | pageMap.set(tex, pages.length); 95 | ltick(); 96 | #if hl hl.Gc.enable(false); #end 97 | pages.push(h2d.Tile.fromTexture(tex)); 98 | ltick(); 99 | // Note: avoid gc_mark() before ltick (PNG read + gc_mark in the same frame) 100 | #if hl hl.Gc.enable(true); #end 101 | } 102 | } 103 | 104 | // load normals 105 | var nrmPages = []; 106 | for (i in 0...pages.length) { 107 | var name = pages[i].getTexture().name; 108 | var nrmName = name.substr(0, name.length - 4) + "_n.png"; 109 | ltick(); 110 | #if hl hl.Gc.enable(false); #end 111 | nrmPages[i] = hxd.res.Loader.currentInstance.exists(nrmName) 112 | ? h2d.Tile.fromTexture(hxd.Res.load(nrmName).toTexture()) 113 | : null; 114 | ltick(); 115 | // Note: avoid gc_mark() before ltick (PNG read + gc_mark in the same frame) 116 | #if hl hl.Gc.enable(true); #end 117 | } 118 | 119 | var lib = new dn.heaps.slib.SpriteLib(pages, nrmPages); 120 | 121 | var frameReg = ~/(.*?)(_?)([0-9]+)$/gi; 122 | var numReg = ~/^[0-9]+$/; 123 | var notZeroBasedWildcard = notZeroBaseds.exists('*'); 124 | for( groupName in bestVariants.keys() ) { 125 | var rawName = bestVariants.get(groupName).rawName; 126 | 127 | var content = contents.get(rawName); 128 | if (content.length == 1) { 129 | var e = content[0]; 130 | var page = pageMap.get(e.t.getTexture()); 131 | 132 | // Parse terminal number 133 | var k = groupName; 134 | var f = 0; 135 | var regBoth = false; 136 | if( frameReg.match(k) ) { 137 | k = frameReg.matched(1); 138 | f = Std.parseInt(frameReg.matched(3)); 139 | if( notZeroBasedWildcard || notZeroBaseds.exists(k) ) 140 | f--; 141 | 142 | // register frameData under both names for 'idle0', not for 'idle_0' 143 | if( frameReg.matched(2).length == 0 ) regBoth = true; 144 | //regBoth = true; 145 | } 146 | 147 | var fd = lib.sliceCustom( 148 | k, page, f, 149 | e.t.ix, e.t.iy, e.t.iwidth, e.t.iheight, 150 | Math.floor(-e.t.dx), Math.floor(-e.t.dy), e.width, e.height 151 | ); 152 | 153 | if( regBoth ) 154 | lib.resliceCustom(groupName, 0, fd); 155 | 156 | lib.resliceCustom(rawName, 0, fd); // original name 157 | } else { 158 | var k = groupName; 159 | if( k.indexOf("/")>=0 ) k = k.substr( k.lastIndexOf("/")+1 ); 160 | for (i in 0...content.length) { 161 | var e = content[i]; 162 | var page = pageMap.get(e.t.getTexture()); 163 | lib.sliceCustom( 164 | k, page, i, 165 | e.t.ix, e.t.iy, e.t.iwidth, e.t.iheight, 166 | Math.floor(-e.t.dx), Math.floor(-e.t.dy), e.width, e.height 167 | ); 168 | } 169 | } 170 | 171 | } 172 | 173 | ltick(); 174 | 175 | @:privateAccess { 176 | for( id in lib.groups.keys() ){ 177 | var nframes = lib.countFrames(id); 178 | var a = CACHE_ANIMS[nframes]; 179 | if( a == null ) { 180 | a = [for(i in 0...nframes) i]; 181 | if( nframes < 256 ) CACHE_ANIMS[nframes] = a; 182 | } 183 | lib.__defineAnim(id, a); 184 | 185 | var p = id.lastIndexOf("/"); 186 | if( p >= 0 ){ 187 | var id2 = id.substr(p+1); 188 | if( id2 != null && id2.length > 0 && !numReg.match(id2) ){ 189 | if( lib.groups.exists(id2) ) 190 | trace("Warning, duplicate short name: "+id2+" in "+atlasName+":"+id); 191 | lib.groups.set(id2, lib.groups.get(id)); 192 | } 193 | } 194 | } 195 | } 196 | 197 | ltick(); 198 | 199 | return lib; 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/dn/heaps/assets/PixelLookup.hx: -------------------------------------------------------------------------------- 1 | package dn.heaps.assets; 2 | 3 | class PixelLookup extends dn.Process { 4 | public static function fromSlib(slib:dn.heaps.slib.SpriteLib, ?slibPixels:hxd.Pixels, color:dn.Col) : Map { 5 | if( slibPixels==null ) 6 | slibPixels = slib.tile.getTexture().capturePixels(); 7 | 8 | var pixelCoords : Map = new Map(); 9 | var output = new PixelCoord(0,0); 10 | for(group in slib.getGroups()) 11 | for(fd in group.frames) 12 | if( lookupPixelSub(slibPixels, fd.x, fd.y, fd.wid, fd.hei, color, output) ) 13 | pixelCoords.set(fd.uid, output.clone()); 14 | 15 | return pixelCoords; 16 | } 17 | 18 | static function lookupPixelSub(slibPixels:hxd.Pixels, x:Int, y:Int, w:Int, h:Int, col:Col, output:PixelCoord) { 19 | col = col.withAlphaIfMissing(); 20 | for(px in x...x+w) 21 | for(py in y...y+h) 22 | if( slibPixels.getPixel(px,py)==col ) { 23 | output.x = px-x; 24 | output.y = py-y; 25 | return true; 26 | } 27 | 28 | output.x = output.y = -1; 29 | return false; 30 | } 31 | } 32 | 33 | 34 | class PixelCoord { 35 | public var x : Int; 36 | public var y : Int; 37 | 38 | public inline function new(x:Int, y:Int) { 39 | this.x = x; 40 | this.y = y; 41 | } 42 | 43 | @:keep public function tostring() { 44 | return 'PixelCoord($x,$y)'; 45 | } 46 | 47 | public inline function clone() { 48 | return new PixelCoord(x,y); 49 | } 50 | } -------------------------------------------------------------------------------- /src/dn/heaps/assets/TexturePacker.hx: -------------------------------------------------------------------------------- 1 | package dn.heaps.assets; 2 | 3 | #if macro 4 | import haxe.macro.Context; 5 | import haxe.macro.Expr; 6 | #else 7 | import mt.heaps.slib.SpriteLib; 8 | #end 9 | 10 | //this definition can invade other module, so let's stick to privacy for now 11 | private typedef Slice = { 12 | var name : String; 13 | var frame : Int; 14 | var x : Int; 15 | var y : Int; 16 | var wid : Int; 17 | var hei : Int; 18 | var offX : Int; 19 | var offY : Int; 20 | var fwid : Int; 21 | var fhei : Int; 22 | } 23 | 24 | class TexturePacker { 25 | 26 | macro public static function stream(worker:ExprOf, onComplete:ExprOfVoid>, xmlPath:String){ 27 | var e = macro { 28 | var libcb; 29 | var xmlContent, xml, img; 30 | 31 | var xmlTask = new mt.Worker.WorkerTask(function() { 32 | xmlContent = hxd.Res.load($v{xmlPath}).toText(); 33 | xml = new haxe.xml.Fast( Xml.parse(xmlContent) ); 34 | }); 35 | $worker.enqueue(xmlTask); 36 | 37 | var textureTask = new mt.Worker.WorkerTask(function() { 38 | var imgPath = xml.node.TextureAtlas.att.imagePath; 39 | img = hxd.Res.load(imgPath); 40 | }); 41 | $worker.enqueue(textureTask); 42 | 43 | var libTask = new mt.Worker.WorkerTask(function() { 44 | libcb = function() return dn.heaps.slib.assets.TexturePacker.parseXml(xml, img.toTile(), img.toBitmap(), false); 45 | }); 46 | //probleme, c'est le parsing qui est le process le plus couteux et lui on ne peut l'executer sur le thread du worker 47 | //comme c'est deprecated à venir, je laisse ceci de côté 48 | libTask.onComplete = function(){ $onComplete( libcb() ); } 49 | $worker.enqueue(libTask); 50 | } 51 | 52 | //var p = new haxe.macro.Printer(); 53 | //trace(p.printExpr(e)); 54 | return e; 55 | } 56 | 57 | #if !macro 58 | @:noCompletion public static var SLICES : Array = new Array(); 59 | public static function load(xmlPath:String, ?treatFoldersAsPrefixes:Bool=false) : SpriteLib { 60 | var xmlContent = hxd.Res.load(xmlPath).toText(); 61 | var xml = new haxe.xml.Fast( Xml.parse(xmlContent) ); 62 | var imgPath = xml.node.TextureAtlas.att.imagePath; 63 | var img = hxd.Res.load(imgPath); 64 | return parseXml(xmlContent, img.toTile(), img.toBitmap(), treatFoldersAsPrefixes); 65 | } 66 | 67 | static inline function makeChecksum(slice:Slice) : String { 68 | return slice.name+","+slice.x+","+slice.y+","+slice.wid+","+slice.hei+","+slice.offX+","+slice.offY+","+slice.fwid+","+slice.fhei; 69 | } 70 | 71 | @:noCompletion public static function parseXml(?xml:haxe.xml.Fast, ?xmlString:String, t:h2d.Tile, bd:hxd.BitmapData, treatFoldersAsPrefixes:Bool) : SpriteLib { 72 | var lib = new SpriteLib(t,bd); 73 | if( xml == null ) xml = new haxe.xml.Fast( Xml.parse(xmlString) ); 74 | var removeExt = ~/\.(png|gif|jpeg|jpg)/gi; 75 | var leadNumber = ~/([0-9]*)$/; 76 | try { 77 | // Parse frames 78 | var slices : Map = new Map(); 79 | var anims : Map> = new Map(); 80 | for(atlas in xml.nodes.TextureAtlas) { 81 | var last : Slice = null; 82 | var frame = 0; 83 | for(sub in atlas.nodes.SubTexture) { 84 | // Read XML 85 | var slice : Slice = { 86 | name : sub.att.name, 87 | frame : 0, 88 | x : Std.parseInt(sub.att.x), 89 | y : Std.parseInt(sub.att.y), 90 | wid : Std.parseInt(sub.att.width), 91 | hei : Std.parseInt(sub.att.height), 92 | offX : !sub.has.frameX ? 0 : Std.parseInt(sub.att.frameX), 93 | offY : !sub.has.frameY ? 0 : Std.parseInt(sub.att.frameY), 94 | fwid : !sub.has.frameWidth ? Std.parseInt(sub.att.width) : Std.parseInt(sub.att.frameWidth), 95 | fhei : !sub.has.frameHeight ? Std.parseInt(sub.att.height) : Std.parseInt(sub.att.frameHeight), 96 | } 97 | 98 | // Clean-up name 99 | slice.name = removeExt.replace(sub.att.name, ""); 100 | if( slice.name.indexOf("/")>=0 ) 101 | if( treatFoldersAsPrefixes ) 102 | slice.name = StringTools.replace(slice.name, "/", "_"); 103 | else 104 | slice.name = slice.name.substr(slice.name.lastIndexOf("/")+1); 105 | 106 | // Remove leading numbers and "_" 107 | if( leadNumber.match(slice.name) ) { 108 | slice.name = slice.name.substr(0, leadNumber.matchedPos().pos); 109 | while( slice.name.length>0 && slice.name.charAt(slice.name.length-1)=="_" ) // trim leading "_" 110 | slice.name = slice.name.substr(0, slice.name.length-1); 111 | } 112 | 113 | // New serie 114 | if( last == null || last.name != slice.name) 115 | frame = 0; 116 | 117 | var csum = makeChecksum(slice); 118 | if( !slices.exists(csum) ) { 119 | // Not an existing slice 120 | slices.set(csum, frame); 121 | slice.frame = frame; 122 | lib.sliceCustom(slice.name, slice.frame, slice.x, slice.y, slice.wid, slice.hei, {x:slice.offX, y:slice.offY, realWid:slice.fwid, realHei:slice.fhei} ); 123 | // Also slice using the raw name 124 | lib.sliceCustom(sub.att.name, 0, slice.x, slice.y, slice.wid, slice.hei, {x:slice.offX, y:slice.offY, realWid:slice.fwid, realHei:slice.fhei} ); 125 | frame++; 126 | } 127 | 128 | var realFrame = slices.get(csum); 129 | if( !anims.exists(slice.name) ) 130 | anims.set(slice.name, [realFrame]); 131 | else 132 | anims.get(slice.name).push(realFrame); 133 | 134 | SLICES.push(slice); 135 | 136 | last = slice; 137 | } 138 | } 139 | 140 | // Define anims 141 | for(k in anims.keys()) 142 | lib.__defineAnim(k, anims.get(k)); 143 | 144 | } 145 | catch(e:Dynamic) { 146 | throw SLBError.AssetImportFailed(e); 147 | } 148 | 149 | return lib; 150 | } 151 | #end 152 | } 153 | -------------------------------------------------------------------------------- /src/dn/heaps/filter/Crt.hx: -------------------------------------------------------------------------------- 1 | package dn.heaps.filter; 2 | 3 | // --- Filter ------------------------------------------------------------------------------- 4 | class Crt extends h2d.filter.Shader { 5 | /** Scanline texture color (RGB format, defaults to 0xffffff) **/ 6 | public var scanlineColor(default,set) : Col; 7 | 8 | /** Scanline texture opacity (0-1) (defaults to 1.0) **/ 9 | public var scanlineAlpha(get,set) : Float; 10 | 11 | /** Horizontal screen distorsion intensity (0-1), defaults to 0.5 **/ 12 | public var curvatureH(default,set) : Float; 13 | 14 | /** Verticval screen distorsion intensity (0-1), defaults to 0.5 **/ 15 | public var curvatureV(default,set) : Float; 16 | 17 | /** Dark vignetting intensity (0-1), defaults to 0.5 **/ 18 | public var vignetting(default,set) : Float; 19 | 20 | /** Height of the scanlines **/ 21 | public var scanlineSize(default,set) : Int; 22 | 23 | /** Set this method to automatically update scanline size based on your own criterions. It should return the new scanline size. **/ 24 | public var autoUpdateSize: Null< Void->Int > = null; 25 | 26 | var scanlineTex : h3d.mat.Texture; 27 | var invalidated = true; 28 | 29 | /** 30 | @param scanlineSize Height of the scanline overlay texture blocks 31 | **/ 32 | public function new(scanlineSize=2, scanlineColor:Col=0xffffff, alpha=1.0) { 33 | super( new InternalShader() ); 34 | this.scanlineAlpha = alpha; 35 | this.scanlineSize = scanlineSize; 36 | this.scanlineColor = scanlineColor; 37 | curvatureH = 0.5; 38 | curvatureV = 0.5; 39 | } 40 | 41 | /** Force re-creation of the overlay texture (not to be called often!) **/ 42 | inline function invalidate() { 43 | invalidated = true; 44 | } 45 | 46 | inline function set_curvatureH(v:Float) { 47 | return shader.curvature.y = v<=0 ? 99 : 2 + (1-v) * 10; 48 | } 49 | 50 | inline function set_curvatureV(v:Float) { 51 | return shader.curvature.x = v<=0 ? 99 : 2 + (1-v) * 10; 52 | } 53 | 54 | inline function set_vignetting(v:Float) { 55 | return shader.vignetting = v; 56 | } 57 | 58 | inline function set_scanlineSize(v) { 59 | if( scanlineSize!=v ) 60 | invalidate(); 61 | return scanlineSize = v; 62 | } 63 | 64 | inline function set_scanlineColor(v) { 65 | if( scanlineColor!=v ) 66 | invalidate(); 67 | return scanlineColor = v; 68 | } 69 | 70 | inline function set_scanlineAlpha(v:Float) return shader.alpha = v; 71 | inline function get_scanlineAlpha() return shader.alpha; 72 | 73 | override function sync(ctx:h2d.RenderContext, s:h2d.Object) { 74 | super.sync(ctx, s); 75 | 76 | if( !Std.isOfType(s, h2d.Scene) ) 77 | throw "CRT filter should only be attached to a 2D Scene"; 78 | 79 | if( invalidated ) { 80 | invalidated = false; 81 | initTexture(ctx.scene.width, ctx.scene.height); 82 | } 83 | 84 | if( autoUpdateSize!=null && scanlineSize!=autoUpdateSize() ) { 85 | scanlineSize = autoUpdateSize(); 86 | // The invalidation re-render will only occur during next frame, to make sure scene width/height is properly set 87 | } 88 | 89 | } 90 | 91 | function initTexture(screenWid:Float, screenHei:Float) { 92 | // Cleanup 93 | if( scanlineTex!=null ) 94 | scanlineTex.dispose(); 95 | 96 | // Init texture 97 | final neutral = 0xFF808080; 98 | var bd = new hxd.BitmapData(scanlineSize,scanlineSize); 99 | bd.clear(neutral); 100 | for(x in 0...bd.width) 101 | bd.setPixel(x, 0, scanlineColor); 102 | 103 | scanlineTex = h3d.mat.Texture.fromBitmap(bd); 104 | scanlineTex.filter = Nearest; 105 | scanlineTex.wrap = Repeat; 106 | 107 | // Update shader 108 | shader.scanline = scanlineTex; 109 | shader.texelSize.set( 1/screenWid, 1/screenHei ); 110 | shader.uvScale = new hxsl.Types.Vec( screenWid / scanlineTex.width, screenHei / scanlineTex.width ); 111 | } 112 | } 113 | 114 | 115 | // --- Shader ------------------------------------------------------------------------------- 116 | private class InternalShader extends h3d.shader.ScreenShader { 117 | 118 | static var SRC = { 119 | @param var texture : Sampler2D; 120 | @param var scanline : Sampler2D; 121 | 122 | @param var curvature : Vec2; 123 | @param var vignetting : Float; 124 | @param var alpha : Float; 125 | @param var uvScale : Vec2; 126 | @param var texelSize : Vec2; 127 | 128 | function blendOverlay(base:Vec3, blend:Vec3) : Vec3 { 129 | return mix( 130 | 1.0 - 2.0 * (1.0 - base) * (1.0 - blend), 131 | 2.0 * base * blend, 132 | step( base, vec3(0.5) ) 133 | ); 134 | } 135 | 136 | function curve(uv:Vec2) : Vec2 { 137 | var out = uv*2 - 1; 138 | 139 | var offset = abs(out.yx) / curvature; 140 | out = out + out * offset * offset; 141 | 142 | out = out*0.5 + 0.5; 143 | return out; 144 | } 145 | 146 | function vignette(uv:Vec2) : Float { 147 | var off = max( abs(uv.y*2-1) / 4, abs(uv.x*2-1) / 4 ); 148 | return 300 * off*off*off*off*off; 149 | } 150 | 151 | function fragment() { 152 | // Distortion 153 | var uv = curve( input.uv ); 154 | 155 | // Scanlines texture 156 | var sourceColor = texture.get(uv); 157 | var scanlineColor = mix( vec4(0.5), scanline.get(input.uv*uvScale), alpha ); 158 | pixelColor.rgba = vec4( 159 | blendOverlay( sourceColor.rgb, scanlineColor.rgb ), 160 | sourceColor.a 161 | ); 162 | 163 | // Vignetting 164 | pixelColor.rgb *= 1 - vignetting * vignette(input.uv); 165 | 166 | // Clear out-of-bounds pixels 167 | pixelColor.rgba *= 168 | step(0, uv.x) * step(uv.x, 1) * // x 169 | step(0, uv.y) * step(uv.y, 1); // y 170 | } 171 | 172 | }; 173 | } 174 | -------------------------------------------------------------------------------- /src/dn/heaps/filter/CustomTransformation.hx: -------------------------------------------------------------------------------- 1 | package dn.heaps.filter; 2 | 3 | 4 | class CustomTransformation extends h2d.filter.Shader { 5 | /** Horizontal translation in pixels **/ 6 | public var offsetX(default,set): Float; 7 | /** Vertical translation in pixels **/ 8 | public var offsetY(default,set): Float; 9 | 10 | /** Global X scale factor (defaults to 1) **/ 11 | public var scaleX(default,set): Float; 12 | /** Global Y scale factor (defaults to 1) **/ 13 | public var scaleY(default,set): Float; 14 | /** Horizontal "coordinate" used for X scaling (defaults to 0) **/ 15 | public var scalePivotX(default,set): Float; 16 | /** Vertical "coordinate" used for Y scaling (defaults to 0) **/ 17 | public var scalePivotY(default,set): Float; 18 | 19 | /** Left side skew factor (defaults to 1) **/ 20 | public var skewScaleLeft(default,set) : Float; 21 | /** Right side skew factor (defaults to 1) **/ 22 | public var skewScaleRight(default,set) : Float; 23 | /** Upper side skew factor (defaults to 1) **/ 24 | public var skewScaleTop(default,set) : Float; 25 | /** Lower side skew factor (defaults to 1) **/ 26 | public var skewScaleBottom(default,set) : Float; 27 | 28 | /** Rectangular X zoom factor (defaults to 0) **/ 29 | public var rectangularZoomX(default,set) : Float; 30 | 31 | /** Rectangular Y zoom factor (defaults to 0) **/ 32 | public var rectangularZoomY(default,set) : Float; 33 | 34 | public var rectangularZoom(never,set) : Float; 35 | 36 | /** Pivot coordinate (U) for rectangular zoom (defaults to 0.5) **/ 37 | public var rectangularZoomCenterU(default,set) : Float; 38 | 39 | /** Pivot coordinate (V) for rectangular zoom (defaults to 0.5) **/ 40 | public var rectangularZoomCenterV(default,set) : Float; 41 | 42 | 43 | public function new() { 44 | super( new InternalShader() ); 45 | 46 | reset(); 47 | } 48 | 49 | public function reset() { 50 | offsetX = offsetY = 0; 51 | setScale(1); 52 | setScalePivots(0,0); 53 | resetSkews(); 54 | rectangularZoomX = 0; 55 | rectangularZoomY = 0; 56 | rectangularZoomCenterU = 0.5; 57 | rectangularZoomCenterV = 0.5; 58 | } 59 | 60 | public inline function resetSkews() { 61 | skewScaleLeft = 1; 62 | skewScaleRight = 1; 63 | skewScaleTop = 1; 64 | skewScaleBottom = 1; 65 | } 66 | 67 | public inline function setScale(s:Float) { 68 | scaleX = scaleY = s; 69 | } 70 | 71 | public inline function setScalePivots(px:Float, py:Float) { 72 | scalePivotX = px; 73 | scalePivotY = py; 74 | } 75 | 76 | inline function set_offsetX(v:Float) return offsetX = shader.offsetX = v; 77 | inline function set_offsetY(v:Float) return offsetY = shader.offsetY = v; 78 | 79 | inline function set_scaleX(v:Float) return scaleX = shader.scaleX = v; 80 | inline function set_scaleY(v:Float) return scaleY = shader.scaleY = v; 81 | inline function set_scalePivotX(v:Float) return scalePivotX = shader.scalePivotX = v; 82 | inline function set_scalePivotY(v:Float) return scalePivotY = shader.scalePivotY = v; 83 | 84 | inline function set_skewScaleLeft(v:Float) return skewScaleLeft = shader.skewScaleLeft = v; 85 | inline function set_skewScaleRight(v:Float) return skewScaleRight = shader.skewScaleRight = v; 86 | inline function set_skewScaleTop(v:Float) return skewScaleTop = shader.skewScaleTop = v; 87 | inline function set_skewScaleBottom(v:Float) return skewScaleBottom = shader.skewScaleBottom = v; 88 | 89 | inline function set_rectangularZoom(v:Float) return rectangularZoomX = rectangularZoomY = v; 90 | inline function set_rectangularZoomX(v:Float) return rectangularZoomX = shader.rectangularZoomX = v; 91 | inline function set_rectangularZoomY(v:Float) return rectangularZoomY = shader.rectangularZoomY = v; 92 | inline function set_rectangularZoomCenterU(v:Float) return rectangularZoomCenterU = shader.rectangularZoomCenterU = v; 93 | inline function set_rectangularZoomCenterV(v:Float) return rectangularZoomCenterV = shader.rectangularZoomCenterV = v; 94 | 95 | override function sync(ctx : h2d.RenderContext, s : h2d.Object) { 96 | super.sync(ctx, s); 97 | } 98 | 99 | override function draw(ctx:h2d.RenderContext, t:h2d.Tile):h2d.Tile { 100 | shader.texelSize.set( 1/t.width, 1/t.height ); 101 | 102 | return super.draw(ctx, t); 103 | } 104 | } 105 | 106 | 107 | // --- Shader ------------------------------------------------------------------------------- 108 | private class InternalShader extends h3d.shader.ScreenShader { 109 | static var SRC = { 110 | @param var texture : Sampler2D; 111 | @param var texelSize : Vec2; 112 | 113 | @param var offsetX : Float; 114 | @param var offsetY : Float; 115 | 116 | @param var scaleX : Float; 117 | @param var scaleY : Float; 118 | @param var scalePivotX : Float; 119 | @param var scalePivotY : Float; 120 | 121 | @param var skewScaleLeft : Float; 122 | @param var skewScaleRight : Float; 123 | @param var skewScaleTop : Float; 124 | @param var skewScaleBottom : Float; 125 | 126 | @param var rectangularZoomX : Float; 127 | @param var rectangularZoomY : Float; 128 | @param var rectangularZoomCenterU : Float; 129 | @param var rectangularZoomCenterV : Float; 130 | 131 | 132 | /** Get source texture color at given UV, clamping out-of-bounds positions **/ 133 | inline function getSrcColor(uv:Vec2) : Vec4 { 134 | return texture.get(uv) 135 | * vec4( step(0, uv.x) * step(0, 1-uv.x) ) 136 | * vec4( step(0, uv.y) * step(0, 1-uv.y) ); 137 | 138 | } 139 | 140 | function fragment() { 141 | var uv = calculatedUV; 142 | 143 | // Offsets 144 | uv.x -= offsetX * texelSize.x; 145 | uv.y -= offsetY * texelSize.y; 146 | 147 | // Base scaling 148 | uv.x = uv.x / scaleX + scalePivotX - scalePivotX/scaleX; 149 | uv.y = uv.y / scaleY + scalePivotY - scalePivotY/scaleY; 150 | 151 | // Skew scaling 152 | var skewScale = skewScaleLeft + uv.x * ( skewScaleRight - skewScaleLeft ); 153 | uv.y = uv.y / skewScale + 0.5 - 0.5/skewScale; 154 | skewScale = skewScaleTop + uv.y * ( skewScaleBottom - skewScaleTop ); 155 | uv.x = uv.x / skewScale + 0.5 - 0.5/skewScale; 156 | 157 | // Zoom scale 158 | if( rectangularZoomX*rectangularZoomY!=0 ) { 159 | var distX = ( uv.x - rectangularZoomCenterU ) * 0.7; 160 | var distY = ( uv.y - rectangularZoomCenterV ) * 0.7; 161 | uv.x = uv.x + abs(distX) * distX * -rectangularZoomX; 162 | uv.y = uv.y + abs(distY) * distY * -rectangularZoomY; 163 | } 164 | 165 | output.color = getSrcColor(uv); 166 | } 167 | }; 168 | } 169 | -------------------------------------------------------------------------------- /src/dn/heaps/filter/Debug.hx: -------------------------------------------------------------------------------- 1 | package dn.heaps.filter; 2 | 3 | 4 | /** Just paint over the affected object an ugly gradient **/ 5 | class Debug extends h2d.filter.Shader { 6 | public function new() { 7 | var s = new InternalShader(); 8 | super(s); 9 | } 10 | } 11 | 12 | 13 | // --- Shader ------------------------------------------------------------------------------- 14 | private class InternalShader extends h3d.shader.ScreenShader { 15 | static var SRC = { 16 | @param var texture : Sampler2D; 17 | 18 | 19 | function fragment() { 20 | var pixelSize = vec2(0.05, 0.05); // should use actual pixel UV 21 | var bounds = 22 | calculatedUV.x<=pixelSize.x || calculatedUV.x>=1-pixelSize.x || 23 | calculatedUV.y<=pixelSize.y || calculatedUV.y>=1-pixelSize.y ? 1 : 0; 24 | 25 | var curColor = texture.get( calculatedUV ); 26 | pixelColor = vec4( 27 | mix(curColor.r, calculatedUV.x, 0.7), 28 | mix(curColor.g, calculatedUV.y, 0.7), 29 | bounds, 30 | 1 31 | ); 32 | } 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /src/dn/heaps/filter/Edge.hx: -------------------------------------------------------------------------------- 1 | package dn.heaps.filter; 2 | 3 | class Edge extends h2d.filter.Shader { 4 | /** 5 | Contrast threshold required between 2 colors to be considered as an "edge". Value is in [1-21] range: 1=identical, 1.5=some contrast (default), 21=black vs white. 6 | See: https://contrast-ratio.com 7 | **/ 8 | public var contrastThreshold(null,set) : Float; 9 | 10 | /** Multiplier to RGB of pixels that are not detected as "edges". **/ 11 | public var notEdgeRgbMul(null,set) : Float; 12 | 13 | /** Multiplier to Alpha of pixels that are not detected as "edges". **/ 14 | public var notEdgeAlphaMul(null,set) : Float; 15 | 16 | public function new(notEdgeMultiplier=0.5) { 17 | super( new InternalShader() ); 18 | } 19 | 20 | inline function set_contrastThreshold(v) return shader.contrastThreshold = v; 21 | inline function set_notEdgeRgbMul(v) return shader.notEdgeRgbMul = v; 22 | inline function set_notEdgeAlphaMul(v) return shader.notEdgeAlphaMul = v; 23 | 24 | override function draw(ctx:h2d.RenderContext, t:h2d.Tile):h2d.Tile { 25 | shader.pixelSize = hxsl.Types.Vec.fromArray([ 1/t.width, 1/t.height ]); 26 | return super.draw(ctx, t); 27 | } 28 | } 29 | 30 | 31 | // --- Shader ------------------------------------------------------------------------------- 32 | private class InternalShader extends h3d.shader.ScreenShader { 33 | static var SRC = { 34 | @param var texture : Sampler2D; 35 | @param var pixelSize : Vec2; 36 | @param var contrastThreshold : Float = 1.5; 37 | @param var notEdgeRgbMul : Float; 38 | @param var notEdgeAlphaMul : Float; 39 | 40 | /** Get color luminance **/ 41 | inline function getLum(col:Vec4) : Float { 42 | return col.rgb.dot( vec3(0.2126, 0.7152, 0.0722) ); 43 | } 44 | 45 | /** Get contrast ratio between 2 colors in range [1-21] **/ 46 | inline function getContrast(c1:Vec4, c2:Vec4) : Float { 47 | var lum1 = getLum(c1); 48 | var lum2 = getLum(c2); 49 | return ( max(lum1,lum2) + 0.05 ) / ( min(lum1,lum2) + 0.05 ); 50 | } 51 | 52 | /** Return 1 if cur is contrasted with c, and cur is brighter. 0 otherwise.**/ 53 | inline function hasContrast(cur:Vec4, c:Vec4) : Float { 54 | return 55 | step( getLum(c), getLum(cur) ) // 1 if "cur" is brighter than "c" 56 | * step( contrastThreshold, getContrast(cur, c) ); 57 | } 58 | 59 | 60 | function fragment() { 61 | var uv = calculatedUV; 62 | var curColor = texture.get(uv); 63 | 64 | // Read nearby texels 65 | var above = texture.get( vec2(uv.x, uv.y-pixelSize.y) ); 66 | var below = texture.get( vec2(uv.x, uv.y+pixelSize.y) ); 67 | var left = texture.get( vec2(uv.x-pixelSize.x, uv.y) ); 68 | var right = texture.get( vec2(uv.x+pixelSize.x, uv.y) ); 69 | 70 | // This value 1 if current texel is detected as an edge 71 | var edge = max( 72 | max( hasContrast(curColor,above), hasContrast(curColor,below) ), 73 | max( hasContrast(curColor,left), hasContrast(curColor,right) ) 74 | ); 75 | 76 | pixelColor = vec4( 77 | curColor.rgb * max(edge, notEdgeRgbMul), 78 | curColor.a * max(edge, notEdgeAlphaMul) 79 | ); 80 | } 81 | }; 82 | } 83 | -------------------------------------------------------------------------------- /src/dn/heaps/filter/FogTeint.hx: -------------------------------------------------------------------------------- 1 | package dn.heaps.filter; 2 | 3 | class FogTeint extends h2d.filter.Shader { 4 | /** Fog intensity (0-1) **/ 5 | public var intensity(default,set) : Float; 6 | inline function set_intensity(v) return shader.intensity = M.fclamp(v,0,1); 7 | 8 | /** Fog color (RGB) **/ 9 | public var color(default,set) : Col; 10 | inline function set_color(c:Col) { shader.fog.setColor(c); return c; } 11 | 12 | public function new(col:Col, intensity=1.0) { 13 | super( new InternalShader() ); 14 | this.color = col; 15 | this.intensity = intensity; 16 | } 17 | } 18 | 19 | 20 | // --- Shader ------------------------------------------------------------------------------- 21 | private class InternalShader extends h3d.shader.ScreenShader { 22 | static var SRC = { 23 | @param var texture : Sampler2D; 24 | @param var fog : Vec3; 25 | @param var intensity : Float; 26 | 27 | inline function getLum(col:Vec3) : Float { 28 | return col.rgb.dot( vec3(0.2126, 0.7152, 0.0722) ); 29 | } 30 | 31 | function fragment() { 32 | var src : Vec4 = texture.get(calculatedUV); 33 | 34 | if( intensity>0 && src.a>0 ) { 35 | var l = getLum(src.rgb); 36 | pixelColor = mix( src.rgba, vec4(fog.rgb, src.a), intensity ); 37 | } 38 | else 39 | pixelColor = src; 40 | } 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /src/dn/heaps/filter/GradientMap.hx: -------------------------------------------------------------------------------- 1 | package dn.heaps.filter; 2 | 3 | private enum AffectedLuminanceRange { 4 | Full; // Default 5 | OnlyLights; 6 | OnlyShadows; 7 | } 8 | 9 | /** 10 | Recolorize an object using the palette provided in a gradient texture. 11 | **/ 12 | class GradientMap extends h2d.filter.Shader { 13 | /** Y pixel coord of the palette in gradient map texture, default=0 **/ 14 | public var paletteY: Int = 0; 15 | 16 | /** Gradient map intensity (0-1) **/ 17 | public var intensity(default,set) : Float; 18 | inline function set_intensity(v) return shader.intensity = M.fclamp(v,0,1); 19 | 20 | /** 21 | @param gradientMap This texture is expected to be a **linear horizontal** color gradient, where left is darkest map color, and right is lightest color. If you have multiple palettes in a single texture, use `paletteY` instance field. 22 | @param intensity Gradient map intensity factor (0-1) 23 | @param mode Determine which range of luminance should be affected: Full (default), OnlyLights, OnlyShadows. 24 | **/ 25 | public function new(gradientMap:h3d.mat.Texture, intensity=1.0, mode:AffectedLuminanceRange = Full) { 26 | super( new InternalShader() ); 27 | 28 | shader.gradientMap = gradientMap; 29 | this.intensity = intensity; 30 | shader.mode = mode.getIndex(); 31 | } 32 | 33 | override function draw(ctx:h2d.RenderContext, t:h2d.Tile):h2d.Tile { 34 | shader.paletteY = M.fclamp( paletteY * 1/t.height, 0, 1 ); 35 | return super.draw(ctx, t); 36 | } 37 | 38 | public static function createGradientMapTexture(darkest:Col, brightest:Col) : h3d.mat.Texture { 39 | var p = hxd.Pixels.alloc(256,1, RGBA); 40 | for(x in 0...p.width) 41 | p.setPixel( x, 0, paletteInterpolation(x/(p.width-1), darkest, brightest) ); 42 | return h3d.mat.Texture.fromPixels(p); 43 | } 44 | 45 | 46 | static inline function paletteInterpolation(ratio:Float, darkest:Col, brightest:Col, white:Col=0xffffff) : Int { 47 | final lightLimit = 0.78; 48 | if( ratio<=lightLimit ) 49 | return darkest.interpolate(brightest, ratio/lightLimit).withAlpha(1); 50 | else 51 | return brightest.interpolate(white, (ratio-lightLimit)/(1-lightLimit)).withAlpha(1); 52 | } 53 | 54 | } 55 | 56 | 57 | // --- Shader ------------------------------------------------------------------------------- 58 | private class InternalShader extends h3d.shader.ScreenShader { 59 | static var SRC = { 60 | @param var texture : Sampler2D; 61 | @param var gradientMap : Sampler2D; 62 | @const var mode : Int; 63 | @param var intensity : Float; 64 | @param var paletteY: Float = 0; 65 | 66 | inline function getLum(col:Vec3) : Float { 67 | return col.rgb.dot( vec3(0.2126, 0.7152, 0.0722) ); 68 | } 69 | 70 | function fragment() { 71 | var pixel : Vec4 = texture.get(calculatedUV); 72 | 73 | if( intensity>0 ) { 74 | var lum = getLum(pixel.rgb); 75 | var rep = gradientMap.get( vec2(lum, paletteY) ); 76 | 77 | if( mode==0 ) // Full gradient map 78 | pixelColor = vec4( mix(pixel.rgb, rep.rgb, intensity ), pixel.a); 79 | else if( mode==1 ) // Only lights 80 | pixelColor = vec4( mix(pixel.rgb, rep.rgb, intensity*lum ), pixel.a); 81 | else // Only shadows 82 | pixelColor = vec4( mix(pixel.rgb, rep.rgb, intensity*(1-lum) ), pixel.a); 83 | } 84 | else 85 | pixelColor = pixel; 86 | 87 | } 88 | }; 89 | } 90 | -------------------------------------------------------------------------------- /src/dn/heaps/filter/Invert.hx: -------------------------------------------------------------------------------- 1 | package dn.heaps.filter; 2 | 3 | 4 | class Invert extends h2d.filter.Shader { 5 | public function new() { 6 | super( new InternalShader() ); 7 | } 8 | } 9 | 10 | 11 | // --- Shader ------------------------------------------------------------------------------- 12 | private class InternalShader extends h3d.shader.ScreenShader { 13 | static var SRC = { 14 | @param var texture : Sampler2D; 15 | 16 | function fragment() { 17 | var col = texture.get(input.uv); 18 | col.r = 1-col.r; 19 | col.g = 1-col.g; 20 | col.b = 1-col.b; 21 | col.rgb *= col.a; 22 | output.color = col; 23 | } 24 | }; 25 | } -------------------------------------------------------------------------------- /src/dn/heaps/filter/Monochrome.hx: -------------------------------------------------------------------------------- 1 | package dn.heaps.filter; 2 | 3 | 4 | class Monochrome extends h2d.filter.Shader { 5 | public var dark(default,set) : Col; 6 | public var light(default,set) : Col; 7 | public var threshold(default,set) : Float; 8 | 9 | public function new(dark:Col=0x0, light:Col=0xffffff, threshold=0.28) { 10 | super( new InternalShader() ); 11 | this.dark = dark; 12 | this.light = light; 13 | this.threshold = threshold; 14 | } 15 | 16 | inline function set_dark(v:Col) { 17 | dark = v; 18 | shader.dark = v.withoutAlpha().toShaderVec3(); 19 | return v; 20 | } 21 | 22 | inline function set_light(v:Col) { 23 | light = v; 24 | shader.light = v.withoutAlpha().toShaderVec3(); 25 | return v; 26 | } 27 | 28 | inline function set_threshold(v:Float) { 29 | return shader.threshold = threshold = v; 30 | } 31 | } 32 | 33 | 34 | // --- Shader ------------------------------------------------------------------------------- 35 | private class InternalShader extends h3d.shader.ScreenShader { 36 | static var SRC = { 37 | @param var texture : Sampler2D; 38 | @param var dark : Vec3; 39 | @param var light : Vec3; 40 | @param var threshold : Float; 41 | 42 | inline function getLum(col:Vec4) : Float { 43 | return col.rgb.dot( vec3(0.2126, 0.7152, 0.0722) ); 44 | } 45 | 46 | function fragment() { 47 | var col : Vec4 = texture.get(input.uv); 48 | var isLight = step( threshold, getLum(col) ); 49 | col.rgb = (1-isLight)*dark + isLight*light; 50 | col.rgb *= col.a; 51 | output.color = col; 52 | } 53 | }; 54 | } -------------------------------------------------------------------------------- /src/dn/heaps/filter/MotionBlur.hx: -------------------------------------------------------------------------------- 1 | package dn.heaps.filter; 2 | 3 | 4 | class MotionBlur extends h2d.filter.Shader { 5 | /** If TRUE, the outline may be drawn outside of the bounds of the object texture, increasing its size. **/ 6 | public var extendBounds = false; 7 | 8 | var blurX(default,set): Float; 9 | var blurY(default,set): Float; 10 | var isZoomBlur(default,set): Bool; 11 | public var ramps(default,set): Int; 12 | public var zoomBlurOriginU(default,set): Float; 13 | public var zoomBlurOriginV(default,set): Float; 14 | 15 | public function new(ramps=4) { 16 | super( new InternalShader() ); 17 | this.ramps = ramps; 18 | resetBlur(); 19 | } 20 | 21 | public inline function resetBlur() { 22 | blurX = 0; 23 | blurY = 0; 24 | isZoomBlur = false; 25 | zoomBlurOriginU = 0.5; 26 | zoomBlurOriginV = 0.5; 27 | } 28 | 29 | public inline function setHorizontalBlur(distPx:Float) { 30 | blurX = distPx; 31 | blurY = 0; 32 | isZoomBlur = false; 33 | } 34 | 35 | public inline function setVerticalBlur(distPx:Float) { 36 | blurX = 0; 37 | blurY = distPx; 38 | isZoomBlur = false; 39 | } 40 | 41 | public inline function setZoomBlur(distPx:Float) { 42 | blurX = blurY = distPx; 43 | isZoomBlur = true; 44 | } 45 | 46 | inline function set_ramps(v:Int) return ramps = shader.ramps = v; 47 | inline function set_blurX(v:Float) return blurX = shader.blurX = v; 48 | inline function set_blurY(v:Float) return blurY = shader.blurY = v; 49 | 50 | inline function set_zoomBlurOriginU(v:Float) return zoomBlurOriginU = shader.zoomBlurOriginU = v; 51 | inline function set_zoomBlurOriginV(v:Float) return zoomBlurOriginV = shader.zoomBlurOriginV = v; 52 | 53 | inline function set_isZoomBlur(v:Bool) { 54 | isZoomBlur = v; 55 | shader.isZoomBlur = v ? 1 : 0; 56 | return v; 57 | } 58 | 59 | override function sync(ctx : h2d.RenderContext, s : h2d.Object) { 60 | super.sync(ctx, s); 61 | boundsExtend = extendBounds ? M.fmax( M.fabs(blurX),M.fabs(blurY) ) : 0; 62 | } 63 | 64 | override function draw(ctx : h2d.RenderContext, t : h2d.Tile) { 65 | shader.texelSize.set( 1/t.width, 1/t.height ); 66 | return super.draw(ctx,t); 67 | } 68 | } 69 | 70 | 71 | // --- Shader ------------------------------------------------------------------------------- 72 | private class InternalShader extends h3d.shader.ScreenShader { 73 | static var SRC = { 74 | @param var texture : Sampler2D; 75 | @param var texelSize : Vec2; 76 | 77 | @param var blurX : Float; 78 | @param var blurY : Float; 79 | 80 | @param var isZoomBlur : Int; 81 | @param var zoomBlurOriginU : Float; 82 | @param var zoomBlurOriginV : Float; 83 | 84 | @param var ramps : Int; 85 | 86 | 87 | inline function getLum(col:Vec3) : Float { 88 | return max(col.r, max(col.g, col.b)); 89 | } 90 | 91 | function fragment() { 92 | var curColor : Vec4 = texture.get(input.uv); 93 | 94 | for(i in 0...ramps) { 95 | var iLocal = i; // force local copy 96 | var r : Float = (iLocal+1) / ramps; 97 | 98 | if( isZoomBlur==1 ) { 99 | // Zoom blur 100 | var ang = atan(input.uv.y-zoomBlurOriginV, input.uv.x-zoomBlurOriginU); 101 | var dist = distance( input.uv, vec2(zoomBlurOriginU, zoomBlurOriginV) ); 102 | var neighColor = texture.get( 103 | input.uv + 104 | vec2( 105 | texelSize.x * -blurX * dist/0.6 * (0.2+0.8*r) * cos(ang), 106 | texelSize.x * -blurY * dist/0.6 * (0.2+0.8*r) * sin(ang) 107 | ) 108 | ); 109 | curColor = mix( curColor, neighColor, 0.1+0.9*(1-r) ); 110 | } 111 | else { 112 | // X/Y blurs 113 | var neighColor = texture.get( input.uv + vec2(texelSize.x*-blurX*(r*r-0.5), texelSize.y*-blurY*(r*r-0.5) ) ); 114 | curColor = mix( curColor, neighColor, 0.1+0.9*(1-r) ); 115 | } 116 | } 117 | 118 | // Increase importance of original pixels 119 | if( isZoomBlur==0 ) { 120 | var original : Vec4 = texture.get(input.uv); 121 | var lum = getLum(original.rgb); 122 | curColor = mix( curColor, original, 0.8*lum ); 123 | } 124 | 125 | output.color = curColor; 126 | } 127 | }; 128 | } -------------------------------------------------------------------------------- /src/dn/heaps/filter/OverlayTexture.hx: -------------------------------------------------------------------------------- 1 | package dn.heaps.filter; 2 | 3 | enum OverlayTextureStyle { 4 | /** Classic bevel (white on top-left, black on bottom-right)**/ 5 | Classic; 6 | 7 | /** Low alpha on dark sides **/ 8 | Soft; 9 | 10 | /** Only white at top of block **/ 11 | Top; 12 | 13 | /** 2px wide bevel **/ 14 | Deep; 15 | 16 | /** Horizontal light scanline **/ 17 | ScanlineLight; 18 | 19 | /** Horizontal dark scanline **/ 20 | ScanlineDark; 21 | } 22 | 23 | // --- Filter ------------------------------------------------------------------------------- 24 | class OverlayTexture extends h2d.filter.Shader { 25 | /** Opacity (0-1) of the overlay texture (defaults to 1.0) **/ 26 | public var alpha(get,set) : Float; 27 | 28 | /** Width / height of texture blocks **/ 29 | public var size(default,set) : Int; 30 | 31 | /** Texture style (see `OverlayTextureStyle` enum)**/ 32 | public var textureStyle(default,set) : OverlayTextureStyle; 33 | 34 | /** Set this method to automatically update texture size based on your own criterions. It should return the expected texture block size (see `size` field). **/ 35 | public var autoUpdateSize: Null< Void->Int > = null; 36 | 37 | var overlayTex : hxsl.Types.Sampler2D; 38 | var invalidated = true; 39 | 40 | /** 41 | @param style Style of the overlay texture (Classic, Soft, Top or Deep) ; see `OverlayTextureStyle` enum 42 | @param sizePx Width & height of the overlay texture blocks 43 | **/ 44 | public function new(style:OverlayTextureStyle = Classic, sizePx=2) { 45 | super( new OverlayBlendShader() ); 46 | alpha = 1; 47 | textureStyle = style; 48 | size = sizePx; 49 | } 50 | 51 | /** Force re-creation of the overlay texture (not to be called often!) **/ 52 | inline function invalidate() { 53 | invalidated = true; 54 | } 55 | 56 | inline function set_size(v) { 57 | if( size!=v ) 58 | invalidate(); 59 | return size = v; 60 | } 61 | 62 | inline function set_textureStyle(v) { 63 | if( textureStyle!=v ) 64 | invalidate(); 65 | return textureStyle = v; 66 | } 67 | inline function set_alpha(v) return shader.alpha = v; 68 | inline function get_alpha() return shader.alpha; 69 | 70 | override function sync(ctx:h2d.RenderContext, s:h2d.Object) { 71 | super.sync(ctx, s); 72 | 73 | if( !Std.isOfType(s, h2d.Scene) ) 74 | throw "OverlayTextureFilter should only be attached to a 2D Scene"; 75 | 76 | if( invalidated ) { 77 | invalidated = false; 78 | renderTexture(ctx.scene.width, ctx.scene.height); 79 | } 80 | 81 | if( autoUpdateSize!=null && size!=autoUpdateSize() ) { 82 | size = autoUpdateSize(); 83 | // The invalidation will occur on next frame, to make sure scene width/height is properly set 84 | } 85 | 86 | } 87 | 88 | function renderTexture(screenWid:Float, screenHei:Float) { 89 | // Cleanup 90 | if( overlayTex!=null ) 91 | overlayTex.dispose(); 92 | 93 | // Init overlay texture 94 | var bd = new hxd.BitmapData(size,size); 95 | bd.clear(0xFF808080); 96 | switch textureStyle { 97 | case ScanlineLight: 98 | for(x in 0...size) 99 | bd.setPixel(x, 0, 0xFFffffff); 100 | 101 | case ScanlineDark: 102 | for(x in 0...size) 103 | bd.setPixel(x, 0, 0xFF000000); 104 | 105 | case Classic: 106 | for(x in 0...size-1) { 107 | bd.setPixel(x, 0, 0xFFffffff); 108 | bd.setPixel(x+1, size-1, 0xFF000000); 109 | } 110 | for(y in 0...size-1) { 111 | bd.setPixel(0, y, 0xffffffff); 112 | bd.setPixel(size-1, y+1, 0xff333333); 113 | } 114 | 115 | case Soft: 116 | for(x in 0...size-1) { 117 | bd.setPixel(x, 0, 0xFFffffff); 118 | bd.setPixel(x+1, size-1, 0xff666666); 119 | } 120 | for(y in 0...size-1) { 121 | bd.setPixel(0, y, 0xFFffffff); 122 | bd.setPixel(size-1, y+1, 0xff666666); 123 | } 124 | 125 | case Top: 126 | for(x in 0...size-1) { 127 | bd.setPixel(x,0, 0xFFffffff); 128 | bd.setPixel(x,size-1, 0xFF000000); 129 | } 130 | 131 | case Deep: 132 | for(pad in 0...2) { 133 | for(x in pad...size-1-pad) { 134 | bd.setPixel(x, pad, 0xFFffffff); 135 | bd.setPixel(x+1, size-1-pad, 0xFF000000); 136 | } 137 | for(y in pad...size-1-pad) { 138 | bd.setPixel(pad, y, 0xffffffff); 139 | bd.setPixel(size-1-pad, y+1, 0xff333333); 140 | } 141 | } 142 | 143 | } 144 | overlayTex = hxsl.Types.Sampler2D.fromBitmap(bd); 145 | overlayTex.filter = Nearest; 146 | overlayTex.wrap = Repeat; 147 | 148 | // Update shader 149 | shader.overlay = overlayTex; 150 | shader.uvScale = new hxsl.Types.Vec( screenWid / overlayTex.width, screenHei / overlayTex.width ); 151 | } 152 | } 153 | 154 | 155 | // --- Shader ------------------------------------------------------------------------------- 156 | private class OverlayBlendShader extends h3d.shader.ScreenShader { 157 | 158 | static var SRC = { 159 | @param var texture : Sampler2D; 160 | @param var overlay : Sampler2D; 161 | 162 | @param var alpha : Float; 163 | @param var uvScale : Vec2; 164 | 165 | function blendOverlay(base:Vec3, blend:Vec3) : Vec3 { 166 | return mix( 167 | 1.0 - 2.0 * (1.0 - base) * (1.0 - blend), 168 | 2.0 * base * blend, 169 | step( base, vec3(0.5) ) 170 | ); 171 | } 172 | 173 | function fragment() { 174 | var sourceColor = texture.get(input.uv); 175 | var overlayColor = mix( vec4(0.5), overlay.get(input.uv*uvScale), alpha ); 176 | 177 | pixelColor.rgba = vec4( 178 | blendOverlay( sourceColor.rgb, overlayColor.rgb ), 179 | sourceColor.a 180 | ); 181 | } 182 | 183 | }; 184 | } 185 | -------------------------------------------------------------------------------- /src/dn/heaps/filter/PixelOutline.hx: -------------------------------------------------------------------------------- 1 | package dn.heaps.filter; 2 | 3 | 4 | /** 5 | Add a 1px pixel-perfect outline around h2d.Object 6 | **/ 7 | class PixelOutline extends h2d.filter.Shader { 8 | /** Outline color (0xRRGGBB) **/ 9 | public var color(default, set) : Col; 10 | public var alpha(default, set) : Float; 11 | 12 | /** If TRUE, the original object pixels are discarded, and only the outline remains **/ 13 | public var knockOut(default,set) : Bool; 14 | 15 | /** If TRUE, the outline may be drawn outside of the bounds of the object texture, increasing its size. **/ 16 | public var extendBounds = true; 17 | 18 | /** Show left pixels of the outline (default is true) **/ 19 | public var left(default,set) : Bool; 20 | /** Show right pixels of the outline (default is true) **/ 21 | public var right(default,set) : Bool; 22 | /** Show top pixels of the outline (default is true) **/ 23 | public var top(default,set) : Bool; 24 | /** Show bottom pixels of the outline (default is true) **/ 25 | public var bottom(default,set) : Bool; 26 | 27 | /** Add a pixel-perfect outline around a h2d.Object using a shader filter **/ 28 | public function new(color:Col=0x0, a=1.0, knockOut=false) { 29 | super( new InternalShader() ); 30 | this.color = color; 31 | alpha = a; 32 | smooth = false; 33 | left = true; 34 | right = true; 35 | top = true; 36 | bottom = true; 37 | this.knockOut = knockOut; 38 | } 39 | 40 | inline function set_color(v:Col) { 41 | color = v; 42 | shader.outlineColor = hxsl.Types.Vec4.fromColor(color); 43 | shader.outlineColor.a = alpha; 44 | return v; 45 | } 46 | 47 | inline function set_alpha(v:Float) { 48 | alpha = v; 49 | shader.outlineColor.a = v; 50 | return v; 51 | } 52 | 53 | inline function set_knockOut(v) { 54 | knockOut = v; 55 | shader.knockOutThreshold = knockOut ? 0 : 1; 56 | return v; 57 | } 58 | 59 | inline function set_left(v) { 60 | left = v; 61 | shader.leftMul = v ? 1 : 0; 62 | return v; 63 | } 64 | 65 | inline function set_right(v) { 66 | right = v; 67 | shader.rightMul = v ? 1 : 0; 68 | return v; 69 | } 70 | 71 | inline function set_top(v) { 72 | top = v; 73 | shader.topMul = v ? 1 : 0; 74 | return v; 75 | } 76 | 77 | inline function set_bottom(v) { 78 | bottom = v; 79 | shader.bottomMul = v ? 1 : 0; 80 | return v; 81 | } 82 | 83 | /** Decrease alpha of non-outline pixels **/ 84 | public function setPartialKnockout(alphaMul:Float) { 85 | alphaMul = M.fclamp(alphaMul, 0, 1); 86 | knockOut = alphaMul<1; 87 | shader.knockOutThreshold = alphaMul; 88 | } 89 | 90 | override function sync(ctx : h2d.RenderContext, s : h2d.Object) { 91 | super.sync(ctx, s); 92 | boundsExtend = extendBounds ? 1 : 0; 93 | } 94 | 95 | override function draw(ctx : h2d.RenderContext, t : h2d.Tile) { 96 | shader.texelSize.set( 1/t.width, 1/t.height ); 97 | return super.draw(ctx,t); 98 | } 99 | } 100 | 101 | 102 | // --- Shader ------------------------------------------------------------------------------- 103 | private class InternalShader extends h3d.shader.ScreenShader { 104 | static var SRC = { 105 | @param var texture : Sampler2D; 106 | @param var texelSize : Vec2; 107 | @param var outlineColor : Vec4; 108 | @param var knockOutThreshold : Float; 109 | 110 | @param var leftMul : Float; 111 | @param var rightMul : Float; 112 | @param var topMul: Float; 113 | @param var bottomMul: Float; 114 | 115 | function fragment() { 116 | var curColor : Vec4 = texture.get(input.uv); 117 | 118 | var onEdge = ( // Get outline color multiplier based on transparent surrounding pixels 119 | ( 1-curColor.a ) * max( 120 | texture.get( vec2(input.uv.x+texelSize.x, input.uv.y) ).a * leftMul, // left outline 121 | max( 122 | texture.get( vec2(input.uv.x-texelSize.x, input.uv.y) ).a * rightMul, // right outline 123 | max( 124 | texture.get( vec2(input.uv.x, input.uv.y+texelSize.y) ).a * topMul, // top outline 125 | texture.get( vec2(input.uv.x, input.uv.y-texelSize.y) ).a * bottomMul // bottom outline 126 | ) 127 | ) 128 | ) 129 | ); 130 | 131 | // Apply color, including outline alpha 132 | var a = max(onEdge*outlineColor.a, min(knockOutThreshold, curColor.a)); 133 | output.color = vec4( 134 | mix( curColor.rgb, outlineColor.rgb, onEdge )*a, 135 | a 136 | ); 137 | } 138 | }; 139 | } -------------------------------------------------------------------------------- /src/dn/heaps/filter/RadialColor.hx: -------------------------------------------------------------------------------- 1 | package dn.heaps.filter; 2 | 3 | 4 | class RadialColor extends h2d.filter.Shader { 5 | /** Color replacement intensity for pixels closer to the center of the texture (0-1) **/ 6 | public var innerIntensity(default,set): Float; 7 | 8 | /** Color replacement intensity for pixels further from the center of the texture (0-1) **/ 9 | public var outerIntensity(default,set): Float; 10 | 11 | /** Threshold when switching from inner to outer intensities (defaults to 0.5) **/ 12 | public var threshold(default,set): Float; 13 | 14 | 15 | public function new(color:Col=0x0, alpha=0., inner=0., outer=0.) { 16 | super( new InternalShader() ); 17 | threshold = 0.5; 18 | innerIntensity = inner; 19 | outerIntensity = outer; 20 | setColor(color, alpha); 21 | } 22 | 23 | /** Change color **/ 24 | public inline function setColor(c:Col, a=1.0) { 25 | shader.color = c.withAlpha(a).toShaderVec4(); 26 | } 27 | 28 | inline function set_innerIntensity(v:Float) return innerIntensity = shader.innerIntensity = M.fclamp(v,0,1); 29 | inline function set_outerIntensity(v:Float) return outerIntensity = shader.outerIntensity = M.fclamp(v,0,1); 30 | inline function set_threshold(v:Float) { 31 | threshold = v; 32 | shader.threshold = M.fmax(v, 0.001); 33 | return v; 34 | } 35 | } 36 | 37 | 38 | // --- Shader ------------------------------------------------------------------------------- 39 | private class InternalShader extends h3d.shader.ScreenShader { 40 | static var SRC = { 41 | @param var texture : Sampler2D; 42 | 43 | @param var color : Vec4; 44 | @param var innerIntensity : Float; 45 | @param var outerIntensity : Float; 46 | @param var threshold : Float; 47 | 48 | 49 | function fragment() { 50 | var uv = input.uv; 51 | var src : Vec4 = texture.get(uv); 52 | output.color = mix( 53 | mix( src, color, innerIntensity ), 54 | mix( src, color, outerIntensity ), 55 | clamp( distance(vec2(0.5),uv) / threshold, 0, 1 ) 56 | ); 57 | } 58 | }; 59 | } -------------------------------------------------------------------------------- /src/dn/heaps/slib/HSpriteBE.hx: -------------------------------------------------------------------------------- 1 | package dn.heaps.slib; 2 | 3 | import h2d.Drawable; 4 | import h2d.SpriteBatch; 5 | import dn.heaps.slib.SpriteLib; 6 | import dn.heaps.slib.SpriteInterface; 7 | import dn.M; 8 | 9 | class HSpriteBE extends BatchElement implements SpriteInterface { 10 | public var anim(get,never) : AnimManager; 11 | var _animManager : AnimManager; 12 | 13 | public var lib : SpriteLib; 14 | public var groupName : String; 15 | public var group : LibGroup; 16 | public var frame : Int; 17 | public var frameData : FrameData; 18 | public var pivot : SpritePivot; 19 | public var destroyed : Bool; 20 | public var animAllocated(get,never) : Bool; 21 | 22 | public var onAnimManAlloc : NullVoid>; 23 | public var onFrameChange: NullVoid>; 24 | 25 | var allocated : Bool; 26 | 27 | public function new(sb:HSpriteBatch, l:SpriteLib, g:String, ?f=0) { 28 | super( l.tile.clone() ); 29 | destroyed = false; 30 | 31 | pivot = new SpritePivot(); 32 | 33 | sb.add(this); 34 | set(l,g,f); 35 | } 36 | 37 | @:allow(HSpriteBatch) 38 | function onAdd(){ 39 | if( allocated ) return; 40 | allocated = true; 41 | if( lib != null ) 42 | lib.addChild( this ); 43 | } 44 | 45 | @:allow(HSpriteBatch) 46 | function onRemove(){ 47 | if( !allocated ) return; 48 | allocated = false; 49 | if( lib != null ) 50 | lib.removeChild( this ); 51 | } 52 | 53 | inline function get_anim() { 54 | if( _animManager==null ) { 55 | _animManager = new AnimManager(this); 56 | if( batch!=null ) 57 | batch.hasUpdate = true; 58 | if( onAnimManAlloc!=null ) 59 | onAnimManAlloc(_animManager); 60 | } 61 | 62 | return _animManager; 63 | } 64 | 65 | inline function get_animAllocated() return _animManager!=null; 66 | 67 | public inline function disposeAnimManager() { 68 | if( animAllocated ) { 69 | _animManager.destroy(); 70 | _animManager = null; 71 | } 72 | } 73 | 74 | public function toString() return "HSpriteBE_"+groupName+"["+frame+"]"; 75 | 76 | public inline function set( ?l:SpriteLib, ?g:String, ?f=0, ?stopAllAnims=false ) { 77 | var changed = false; 78 | if( l!=null && lib!=l ) { 79 | changed = true; 80 | // Reset existing frame data 81 | if( g==null ) { 82 | groupName = null; 83 | group = null; 84 | frameData = null; 85 | } 86 | 87 | // Register SpriteLib 88 | if( allocated && lib!=null ) 89 | lib.removeChild(this); 90 | lib = l; 91 | if( allocated ) 92 | lib.addChild(this); 93 | if( pivot.isUndefined ) 94 | setCenterRatio(lib.defaultCenterX, lib.defaultCenterY); 95 | } 96 | 97 | if( g!=null && g!=groupName ) { 98 | changed = true; 99 | groupName = g; 100 | } 101 | 102 | if( f!=null && f!=frame ) { 103 | changed = true; 104 | frame = f; 105 | } 106 | 107 | if( isReady() && changed ) { 108 | if( stopAllAnims && _animManager!=null ) 109 | anim.stopWithoutStateAnims(); 110 | 111 | group = lib.getGroup(groupName); 112 | frameData = lib.getFrameData(groupName, f); 113 | if( frameData==null ) 114 | throw 'Unknown frame: $groupName($f)'; 115 | 116 | updateTile(); 117 | 118 | if( onFrameChange!=null ) 119 | onFrameChange(); 120 | } 121 | } 122 | 123 | 124 | 125 | public inline function setRandom(?l:SpriteLib, g:String, ?rndFunc:Int->Int) { 126 | set(l, g, lib.getRandomFrame(g, rndFunc==null ? Std.random : rndFunc)); 127 | } 128 | 129 | public inline function setRandomFrame(?rndFunc:Int->Int) { 130 | if( isReady() ) 131 | setRandom(groupName, rndFunc==null ? Std.random : rndFunc); 132 | } 133 | 134 | public inline function isGroup(k) { 135 | return groupName==k; 136 | } 137 | 138 | public inline function is(k, ?f=-1) return groupName==k && (f<0 || frame==f); 139 | 140 | public inline function isReady() { 141 | return !destroyed && groupName!=null; 142 | } 143 | 144 | public function fitToBox(w:Float, ?h:Null, ?useFrameDataRealSize=false) { 145 | if( useFrameDataRealSize ) 146 | setScale( M.fmin( w/frameData.realWid, (h==null?w:h)/frameData.realHei ) ); 147 | else 148 | setScale( M.fmin( w/t.width, (h==null?w:h)/t.height ) ); 149 | } 150 | 151 | public inline function setScale(v:Float) scale = v; 152 | public inline function setPos(x:Float, y:Float) setPosition(x,y); 153 | public inline function setPosition(x:Float, y:Float) { 154 | this.x = x; 155 | this.y = y; 156 | } 157 | 158 | public function setFrame(f:Int) { 159 | var changed = f!=frame; 160 | frame = f; 161 | 162 | if( isReady() && changed ) { 163 | frameData = lib.getFrameData(groupName, frame); 164 | if( frameData==null ) 165 | throw 'Unknown frame: $groupName($frame)'; 166 | 167 | if( onFrameChange!=null ) 168 | onFrameChange(); 169 | 170 | updateTile(); 171 | } 172 | } 173 | 174 | public inline function setPivotCoord(x:Float, y:Float) { 175 | pivot.setCoord(x, y); 176 | updateTile(); 177 | } 178 | 179 | public inline function setCenterRatio(xRatio=0.5, yRatio=0.5) { 180 | pivot.setCenterRatio(xRatio, yRatio); 181 | updateTile(); 182 | } 183 | 184 | public inline function totalFrames() { 185 | return group.frames.length; 186 | } 187 | 188 | 189 | public inline function uncolorize() { 190 | dn.legacy.Color.uncolorizeBatchElement(this); 191 | } 192 | public inline function colorize(c:dn.Col, ?ratio=1.0) { 193 | dn.legacy.Color.colorizeBatchElement(this, c, ratio); 194 | } 195 | public inline function isColorized() return r!=1 || g!=1 || b!=1; 196 | 197 | 198 | function updateTile() { 199 | if( !isReady() ) 200 | return; 201 | 202 | 203 | var fd = frameData; 204 | lib.updTile(t, groupName, frame); 205 | 206 | if ( pivot.isUsingCoord() ) { 207 | t.dx = M.round(-pivot.coordX - fd.realX); 208 | t.dy = M.round(-pivot.coordY - fd.realY); 209 | } 210 | 211 | if ( pivot.isUsingFactor() ){ 212 | t.dx = -Std.int(fd.realWid * pivot.centerFactorX + fd.realX); 213 | t.dy = -Std.int(fd.realHei * pivot.centerFactorY + fd.realY); 214 | } 215 | } 216 | 217 | public inline function dispose() remove(); 218 | override function remove() { 219 | super.remove(); 220 | 221 | if( !destroyed ) { 222 | destroyed = true; 223 | disposeAnimManager(); 224 | } 225 | } 226 | 227 | override function update(et:Float) { 228 | if( animAllocated ) 229 | anim.update( lib!=null ? lib.tmod : 1 ); 230 | 231 | return super.update(et); 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /src/dn/heaps/slib/HSpriteBatch.hx: -------------------------------------------------------------------------------- 1 | package dn.heaps.slib; 2 | import h2d.SpriteBatch.BatchElement; 3 | 4 | @:access(h2d.BatchElement) 5 | class HSpriteBatch extends h2d.SpriteBatch { 6 | 7 | override function onAdd(){ 8 | super.onAdd(); 9 | var e = first; 10 | while( e != null ){ 11 | if( Std.isOfType(e,HSpriteBE) ) 12 | Std.downcast(e, HSpriteBE).onAdd(); 13 | e = e.next; 14 | } 15 | } 16 | 17 | override function onRemove(){ 18 | super.onRemove(); 19 | var e = first; 20 | while( e != null ){ 21 | if( Std.isOfType(e,HSpriteBE) ) 22 | Std.downcast(e, HSpriteBE).onRemove(); 23 | e = e.next; 24 | } 25 | } 26 | 27 | override function add( e : BatchElement, before=false ){ 28 | e = super.add(e,before); 29 | if( allocated && Std.isOfType(e,HSpriteBE) ) 30 | Std.downcast(e, HSpriteBE).onAdd(); 31 | return e; 32 | } 33 | 34 | override function delete( e : BatchElement ){ 35 | super.delete(e); 36 | if( allocated && Std.isOfType(e,HSpriteBE) ) 37 | Std.downcast(e, HSpriteBE).onRemove(); 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /src/dn/heaps/slib/SpriteInterface.hx: -------------------------------------------------------------------------------- 1 | package dn.heaps.slib; 2 | 3 | import dn.heaps.slib.SpriteLib; 4 | 5 | interface SpriteInterface { 6 | 7 | public var lib : SpriteLib; 8 | public var group : Null; 9 | public var groupName : Null; 10 | public var frame(default,null) : Int; 11 | public var pivot : SpritePivot; 12 | private var frameData : FrameData; 13 | public var destroyed : Bool; 14 | 15 | public var animAllocated(get,never) : Bool; 16 | private var _animManager : AnimManager; 17 | public var anim(get,never) : AnimManager; 18 | 19 | public var onAnimManAlloc : NullVoid>; 20 | public var onFrameChange : NullVoid>; 21 | 22 | 23 | 24 | private function toString() : String; 25 | public function remove() : Void; 26 | public function isReady() : Bool; 27 | 28 | public function set(?l:SpriteLib, ?g:String, ?frame:Int, ?stopAllAnims:Bool) : Void; 29 | public function setFrame(f:Int) : Void; 30 | public function setRandom(?l:SpriteLib, g:String, ?rndFunc:Int->Int) : Void; 31 | public function setRandomFrame(?rndFunc:Int->Int) : Void; 32 | 33 | public function isGroup(k:String) : Bool; 34 | public function is(k:String, ?f:Int) : Bool; 35 | 36 | public function setScale(#if h2d v:hxd.Float32 #else v:Float #end) : Void; 37 | public function setPos(x:Float, y:Float) : Void; 38 | public function setPivotCoord(x:Float, y:Float) : Void; 39 | public function setCenterRatio(xr:Float=0.5, yr:Float=0.5) : Void; 40 | public function fitToBox(w:Float, ?h:Null, ?useFrameDataRealSize:Bool=false) : Void; 41 | 42 | public function totalFrames() : Int; 43 | 44 | public function disposeAnimManager() : Void; 45 | } 46 | -------------------------------------------------------------------------------- /src/dn/heaps/slib/SpritePivot.hx: -------------------------------------------------------------------------------- 1 | package dn.heaps.slib; 2 | 3 | class SpritePivot { 4 | public var isUndefined(default,null) : Bool; 5 | var usingFactor : Bool; 6 | 7 | public var coordX : Float; 8 | public var coordY : Float; 9 | 10 | public var centerFactorX : Float; 11 | public var centerFactorY : Float; 12 | 13 | public function new() { 14 | isUndefined = true; 15 | } 16 | 17 | public inline function toString() { 18 | return if( isUndefined ) "None"; 19 | else if( isUsingCoord() ) "Coord_"+Std.int(coordX)+","+Std.int(coordY); 20 | else "Factor_"+Std.int(centerFactorX)+","+Std.int(centerFactorY); 21 | } 22 | 23 | public inline function isUsingFactor() return !isUndefined && usingFactor; 24 | public inline function isUsingCoord() return !isUndefined && !usingFactor; 25 | 26 | public inline function setCenterRatio(xr,yr) { 27 | centerFactorX = xr; 28 | centerFactorY = yr; 29 | usingFactor = true; 30 | isUndefined = false; 31 | } 32 | 33 | public inline function setCoord(x,y) { 34 | coordX = x; 35 | coordY = y; 36 | usingFactor = false; 37 | isUndefined = false; 38 | } 39 | 40 | public inline function makeUndefined() { 41 | isUndefined = true; 42 | } 43 | 44 | public inline function copyFrom(from:SpritePivot) { 45 | if( from.isUsingCoord() ) 46 | setCoord(from.coordX, from.coordY); 47 | 48 | if( from.isUsingFactor() ) 49 | setCenterRatio(from.centerFactorX, from.centerFactorY); 50 | } 51 | 52 | public function clone() { 53 | var p = new SpritePivot(); 54 | p.copyFrom(this); 55 | return p; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/dn/js/ElectronDialogs.hx: -------------------------------------------------------------------------------- 1 | package dn.js; 2 | 3 | #if !electron 4 | #error "HaxeLib \"electron\" is required"; 5 | #end 6 | 7 | import electron.main.IpcMain; 8 | import electron.renderer.IpcRenderer; 9 | 10 | class ElectronDialogs { 11 | static function isWindows() { 12 | return js.Node.process.platform.toLowerCase().indexOf("win")==0; 13 | } 14 | 15 | 16 | public static function initMain(browserWindow:electron.main.BrowserWindow) { 17 | if( IpcMain==null ) 18 | throw "Should only be called in Electron Main"; 19 | 20 | IpcMain.handle("openDialog", function(event, options) { 21 | var result = electron.main.Dialog.showOpenDialogSync(browserWindow, options); 22 | return switch Type.typeof(result) { 23 | case TClass(String): result; 24 | case TClass(Array): result[0]; 25 | case _: result; 26 | } 27 | }); 28 | 29 | IpcMain.handle("saveAsDialog", function(event, options) { 30 | var filePaths = electron.main.Dialog.showSaveDialogSync(browserWindow, options); 31 | return filePaths==null ? null : filePaths; 32 | }); 33 | } 34 | 35 | public static function openFile(?extWithDots:Array, startDir:String, onLoad:(filePath:String)->Void) { 36 | if( isWindows() ) 37 | startDir = FilePath.convertToBackslashes(startDir); 38 | 39 | var options = { 40 | filters: extWithDots==null 41 | ? [{ name:"Any file type", extensions:["*"] }] 42 | : [{ name:"Supported file types", extensions:extWithDots.map( function(ext) return ext.substr(1) ) }], 43 | defaultPath: startDir, 44 | } 45 | IpcRenderer.invoke("openDialog", options).then( function(res) { 46 | if( res!=null ) 47 | onLoad( Std.string(res) ); 48 | }); 49 | } 50 | 51 | public static function openDir(startDir:String, onLoad:(filePath:String)->Void) { 52 | if( isWindows() ) 53 | startDir = FilePath.convertToBackslashes(startDir); 54 | 55 | var options = { 56 | defaultPath: startDir, 57 | properties: ["openDirectory"], 58 | } 59 | IpcRenderer.invoke("openDialog", options).then( function(res) { 60 | if( res!=null ) 61 | onLoad( Std.string(res) ); 62 | }); 63 | } 64 | 65 | 66 | public static function saveFileAs(?extWithDots:Array, startDir:String, onFileSelect:(filePath:String)->Void) { 67 | if( isWindows() ) 68 | startDir = FilePath.convertToBackslashes(startDir); 69 | 70 | var options = { 71 | filters: extWithDots==null 72 | ? [{ name:"Any file type", extensions:["*"] }] 73 | : [{ name:"Supported file types", extensions:extWithDots.map( function(ext) return ext.substr(1) ) }], 74 | defaultPath: startDir, 75 | } 76 | IpcRenderer.invoke("saveAsDialog", options).then( function(res) { 77 | if( res!=null ) 78 | onFileSelect( Std.string(res) ); 79 | }); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/dn/js/NodeTools.hx: -------------------------------------------------------------------------------- 1 | package dn.js; 2 | 3 | import js.node.Fs; 4 | 5 | /** 6 | Many useful methods from NodeJS. 7 | 8 | RECOMMENDED: add `import NodeTools as NT;` to your `import.hx` file. 9 | **/ 10 | class NodeTools { 11 | public static inline function isWindows() return js.node.Os.platform()=="win32"; 12 | public static inline function isMacOs() return js.node.Os.platform()=="darwin"; 13 | public static inline function isLinux() return js.node.Os.platform()=="linux"; 14 | 15 | /** Return TRUE if given path exists **/ 16 | public static function fileExists(path:String) { 17 | return path==null || path.length==0 ? false : js.node.Fs.existsSync(path); 18 | } 19 | 20 | /** Return TRUE if given path is a directory **/ 21 | public static function isDirectory(path:String) { 22 | return 23 | try ( fileExists(path) && Fs.statSync(path).isDirectory() ) 24 | catch(_) false; 25 | } 26 | 27 | /** Rename a file and return TRUE if it worked. **/ 28 | public static function renameFile(oldPath:String, newPath:String) : Bool { 29 | if( !fileExists(oldPath) || fileExists(newPath) ) 30 | return false; 31 | else 32 | return 33 | try { js.node.Fs.renameSync(oldPath,newPath); true; } 34 | catch(e:Dynamic) false; 35 | } 36 | 37 | /** Delete a file **/ 38 | public static function removeFile(path:String) : Bool { 39 | if( !fileExists(path) ) 40 | return false; 41 | else { 42 | return 43 | try { js.node.Fs.unlinkSync(path); true; } 44 | catch(e:Dynamic) false; 45 | } 46 | } 47 | 48 | /** Load file content as String **/ 49 | public static function readFileString(path:String) : Null { 50 | if( !fileExists(path) ) 51 | return null; 52 | else 53 | return js.node.Fs.readFileSync(path).toString(); 54 | } 55 | 56 | /** Load file content as Haxe Bytes **/ 57 | public static function readFileBytes(path:String) : Null { 58 | if( !fileExists(path) ) 59 | return null; 60 | else 61 | return js.node.Fs.readFileSync(path).hxToBytes(); 62 | } 63 | 64 | /** Write a String to a file **/ 65 | public static function writeFileString(path:String, str:String) { 66 | js.node.Fs.writeFileSync( path, str ); 67 | } 68 | 69 | /** Write Haxe Bytes to a file **/ 70 | public static function writeFileBytes(path:String, bytes:haxe.io.Bytes) { 71 | js.node.Fs.writeFileSync( path, js.node.Buffer.hxFromBytes(bytes) ); 72 | } 73 | 74 | /** Creates a path recursively **/ 75 | public static function createDirs(dirPath:String) { 76 | if( fileExists(dirPath) ) 77 | return false; 78 | 79 | // Split sub dirs 80 | var subDirs = dn.FilePath.fromDir(dirPath).getSubDirectories(true); 81 | for(subPath in subDirs) 82 | if( !fileExists(subPath) ) 83 | js.node.Fs.mkdirSync(subPath); 84 | return true; 85 | } 86 | 87 | /** Copy a file **/ 88 | public static function copyFile(from:String, to:String) { 89 | if( !fileExists(from) ) 90 | return; 91 | 92 | (cast js.node.Fs).copyFileSync(from, to); 93 | } 94 | 95 | /** Remove a directory **/ 96 | public static function removeDir(path:String) { 97 | if( !isDirectory(path) ) 98 | return; 99 | 100 | js.node.Fs.rmdirSync(path, { 101 | recursive: true, retryDelay: 1000, maxRetries: 3 // WARNING: requires NodeJS 12+ 102 | }); 103 | } 104 | 105 | /** Return TRUE if the directory contains anything: file or sub-dirs (even empty ones) **/ 106 | public static function dirContainsAnything(path:String) { 107 | if( !isDirectory(path) ) 108 | return false; 109 | 110 | for( f in js.node.Fs.readdirSync(path) ) 111 | return true; 112 | 113 | return false; 114 | } 115 | 116 | /** Return TRUE if the directory contains any file (empty sub-dirs will be ignored) **/ 117 | public static function dirContainsAnyFile(path:String) { 118 | if( !isDirectory(path) ) 119 | return false; 120 | 121 | var pendings = [ path ]; 122 | while( pendings.length>0 ) { 123 | var fp = dn.FilePath.fromDir( pendings.shift() ); 124 | for( f in Fs.readdirSync(fp.full) ) { 125 | var inf = Fs.lstatSync(fp.full+"/"+f); 126 | if( !inf.isDirectory() ) 127 | return true; 128 | else if( !inf.isSymbolicLink() ) {} 129 | pendings.push( fp.full+"/"+f ); 130 | } 131 | } 132 | 133 | return false; 134 | } 135 | 136 | /** Delete all FILES in directory AND its sub-dirs **/ 137 | public static function removeAllFilesInDir(path:String, ?onlyExts:Array) { 138 | if( !isDirectory(path) ) 139 | return; 140 | 141 | var extMap = new Map(); 142 | if( onlyExts!=null ) 143 | for(e in onlyExts) 144 | extMap.set(e, true); 145 | 146 | js.node.Require.require("fs"); 147 | var fp = dn.FilePath.fromDir(path); 148 | for(f in js.node.Fs.readdirSync(path)) { 149 | fp.fileWithExt = f; 150 | if( js.node.Fs.lstatSync(fp.full).isFile() && ( onlyExts==null || extMap.exists(fp.extension) ) ) 151 | js.node.Fs.unlinkSync(fp.full); 152 | } 153 | } 154 | 155 | /** List all entries in a given directory **/ 156 | public static function readDir(path:String) : Array { 157 | if( !isDirectory(path) ) 158 | return []; 159 | 160 | js.node.Require.require("fs"); 161 | return try js.node.Fs.readdirSync(path) catch(_) []; 162 | } 163 | 164 | 165 | static function _checkPermission(path:String, mode:Int) { 166 | try { 167 | js.node.Fs.accessSync(path, mode); 168 | return true; 169 | } 170 | catch(_) { 171 | return false; 172 | } 173 | } 174 | 175 | /** 176 | Check RWX permissions of a directory or a file. For some obscure reasons, this won't work well with Windows custom rights. 177 | **/ 178 | public static function checkPermissions(dirOrFilePath:String, read:Bool, write:Bool, execute:Bool) { 179 | if( read && !_checkPermission(dirOrFilePath, Fs.R_OK) ) 180 | return false; 181 | 182 | if( write && !_checkPermission(dirOrFilePath, Fs.W_OK) ) 183 | return false; 184 | 185 | if( execute && !_checkPermission(dirOrFilePath, Fs.X_OK) ) 186 | return false; 187 | 188 | return true; 189 | } 190 | 191 | /** Find all files in given directory and its subdirs **/ 192 | public static function findFilesRecInDir(dirPath:String, ?ext:String) : Array { 193 | if( !isDirectory(dirPath) ) 194 | return []; 195 | 196 | var all = []; 197 | var pendings = [dirPath]; 198 | while( pendings.length>0 ) { 199 | var dir = pendings.shift(); 200 | for(f in readDir(dir)) { 201 | var fp = dn.FilePath.fromFile(dir+"/"+f); 202 | if( js.node.Fs.lstatSync(fp.full).isFile() ) { 203 | if( ext==null || fp.extension==ext ) 204 | all.push(fp); 205 | } 206 | else if( !js.node.Fs.lstatSync(fp.full).isSymbolicLink() ) 207 | pendings.push(fp.full); 208 | } 209 | } 210 | return all; 211 | } 212 | 213 | } 214 | -------------------------------------------------------------------------------- /src/dn/legacy/ControlQueue.hx: -------------------------------------------------------------------------------- 1 | package dn.legacy; 2 | import dn.heaps.input.*; 3 | 4 | class ControlQueue { 5 | var ca : ControllerAccess; 6 | var curTimeS : Float = 0; 7 | 8 | public var queueDurationS(default,null) : Float; 9 | var allWatches : Array = []; 10 | 11 | var lastFrameDown : Map = new Map(); 12 | var holdDurationS : Map = new Map(); 13 | var lastDownS : Map = new Map(); 14 | var lastReleaseS : Map = new Map(); 15 | var upRequired : Map = new Map(); 16 | 17 | public function new(ca:ControllerAccess, watcheds:Array, queueDurationS=0.3) { 18 | this.ca = ca; 19 | this.queueDurationS = queueDurationS; 20 | this.allWatches = watcheds.copy(); 21 | } 22 | 23 | @:keep public function toString() { 24 | return allWatches.map( (c:T)->{ 25 | return c+"=>" 26 | + (downNowOrRecently(c)?"D":"_") 27 | + (releasedNowOrRecently(c)?"R":"_") 28 | + "("+M.pretty(getHoldTimeS(c),1)+"s)"; 29 | } ).join(", "); 30 | } 31 | 32 | public function earlyFrameUpdate(curTimeS:Float) { 33 | var deltaS = this.curTimeS<=0 ? 0 : curTimeS - this.curTimeS; 34 | this.curTimeS = curTimeS; 35 | 36 | for(c in allWatches) { 37 | // Hold duration 38 | if( ca.isDown(c) ) { 39 | if( !wasDownInLastFrame(c) ) 40 | holdDurationS.set(c, 0); 41 | 42 | if( !holdDurationS.exists(c) ) 43 | holdDurationS.set(c, deltaS); 44 | else 45 | holdDurationS.set(c, holdDurationS.get(c)+deltaS); 46 | } 47 | 48 | // Down/up/release 49 | if( upRequired.exists(c) && ca.isDown(c) ) 50 | continue; 51 | 52 | if( upRequired.exists(c) ) 53 | upRequired.remove(c); 54 | 55 | if( ca.isDown(c) ) 56 | lastDownS.set(c, curTimeS); 57 | else if( !ca.isDown(c) && wasDownInLastFrame(c) ) 58 | lastReleaseS.set(c, curTimeS); 59 | 60 | lastFrameDown.set(c, ca.isDown(c)); 61 | } 62 | } 63 | 64 | public function emulateInstantDown(c:T) { 65 | lastDownS.set(c, curTimeS); 66 | } 67 | 68 | inline function wasDownInLastFrame(c:T) { 69 | return lastFrameDown.get(c)==true; 70 | } 71 | 72 | public inline function clear(c:T) { 73 | lastDownS.remove(c); 74 | lastReleaseS.remove(c); 75 | holdDurationS.remove(c); 76 | } 77 | 78 | public function clearAndRequireKeyUp(c:T) { 79 | upRequired.set(c,true); 80 | clear(c); 81 | } 82 | 83 | 84 | public function mostRecentDown() : Null { 85 | var best : T = null; 86 | for(c in allWatches) { 87 | if( downNowOrRecently(c) && ( best==null || getLastDownTime(c)= 4) 4 | enum 5 | #else 6 | @:enum 7 | #end 8 | abstract PadKey(Int) { 9 | var A = 0; 10 | var B = 1; 11 | var X = 2; 12 | var Y = 3; 13 | var SELECT = 4; 14 | var START = 5; 15 | var LT = 6; 16 | var RT = 7; 17 | var LB = 8; 18 | var RB = 9; 19 | var LSTICK = 10; 20 | var RSTICK = 11; 21 | var DPAD_UP = 12; 22 | var DPAD_DOWN = 13; 23 | var DPAD_LEFT = 14; 24 | var DPAD_RIGHT = 15; 25 | var AXIS_LEFT_X = 16; 26 | var AXIS_LEFT_X_NEG = 17; 27 | var AXIS_LEFT_X_POS = 18; 28 | var AXIS_LEFT_Y = 19; 29 | var AXIS_LEFT_Y_NEG = 20; 30 | var AXIS_LEFT_Y_POS = 21; 31 | var AXIS_RIGHT_X = 22; 32 | var AXIS_RIGHT_X_NEG = 23; 33 | var AXIS_RIGHT_X_POS = 24; 34 | var AXIS_RIGHT_Y = 25; 35 | var AXIS_RIGHT_Y_NEG = 26; 36 | var AXIS_RIGHT_Y_POS = 27; 37 | 38 | public inline function new( v : Int ){ 39 | this = v; 40 | } 41 | 42 | public inline function getIndex() : Int { 43 | return this; 44 | } 45 | 46 | inline public static var LENGTH = 28; 47 | } 48 | 49 | class GamePad { 50 | public static var ALL : Array = []; 51 | public static var AVAILABLE_DEVICES : Array; 52 | 53 | static var MAPPING : Array = [ 54 | hxd.Pad.DEFAULT_CONFIG.A, 55 | hxd.Pad.DEFAULT_CONFIG.B, 56 | hxd.Pad.DEFAULT_CONFIG.X, 57 | hxd.Pad.DEFAULT_CONFIG.Y, 58 | hxd.Pad.DEFAULT_CONFIG.back, 59 | hxd.Pad.DEFAULT_CONFIG.start, 60 | hxd.Pad.DEFAULT_CONFIG.LT, 61 | hxd.Pad.DEFAULT_CONFIG.RT, 62 | hxd.Pad.DEFAULT_CONFIG.LB, 63 | hxd.Pad.DEFAULT_CONFIG.RB, 64 | hxd.Pad.DEFAULT_CONFIG.analogClick, 65 | hxd.Pad.DEFAULT_CONFIG.ranalogClick, 66 | hxd.Pad.DEFAULT_CONFIG.dpadUp, 67 | hxd.Pad.DEFAULT_CONFIG.dpadDown, 68 | hxd.Pad.DEFAULT_CONFIG.dpadLeft, 69 | hxd.Pad.DEFAULT_CONFIG.dpadRight, 70 | 71 | hxd.Pad.DEFAULT_CONFIG.analogX, 72 | hxd.Pad.DEFAULT_CONFIG.analogX, 73 | hxd.Pad.DEFAULT_CONFIG.analogX, 74 | 75 | hxd.Pad.DEFAULT_CONFIG.analogY, 76 | hxd.Pad.DEFAULT_CONFIG.analogY, 77 | hxd.Pad.DEFAULT_CONFIG.analogY, 78 | 79 | hxd.Pad.DEFAULT_CONFIG.ranalogX, 80 | hxd.Pad.DEFAULT_CONFIG.ranalogX, 81 | hxd.Pad.DEFAULT_CONFIG.ranalogX, 82 | 83 | hxd.Pad.DEFAULT_CONFIG.ranalogY, 84 | hxd.Pad.DEFAULT_CONFIG.ranalogY, 85 | hxd.Pad.DEFAULT_CONFIG.ranalogY 86 | ]; 87 | 88 | var device : Null; 89 | var toggles : Array; 90 | 91 | public var deadZone : Float = 0.18; 92 | public var axisAsButtonDeadZone : Float = 0.70; 93 | public var lastActivity(default,null) : Float; 94 | 95 | public function new(?deadZone:Float, ?onEnable:GamePad->Void) { 96 | ALL.push(this); 97 | toggles = []; 98 | 99 | if( deadZone!=null ) 100 | this.deadZone = deadZone; 101 | 102 | if( onEnable!=null ) 103 | this.onEnable = onEnable; 104 | 105 | if( AVAILABLE_DEVICES==null ){ 106 | AVAILABLE_DEVICES = []; 107 | hxd.Pad.wait( onDevice ); 108 | } else if( AVAILABLE_DEVICES.length > 0 ){ 109 | enableDevice( AVAILABLE_DEVICES[0] ); 110 | } 111 | 112 | lastActivity = haxe.Timer.stamp(); 113 | } 114 | 115 | public dynamic function onEnable(pad:GamePad) {} 116 | public dynamic function onDisable(pad:GamePad) {} 117 | public inline function isEnabled() return device!=null; 118 | 119 | public inline function toString() return "GamePad("+getDeviceId()+")"; 120 | public inline function getDeviceName() : Null return device==null ? null : device.name; 121 | public inline function getDeviceId() : Null return device==null ? null : device.index; 122 | 123 | function enableDevice( p : hxd.Pad ) { 124 | if( device==null ) { 125 | AVAILABLE_DEVICES.remove( p ); 126 | p.onDisconnect = function(){ 127 | disable(); 128 | } 129 | device = p; 130 | onEnable( this ); 131 | } 132 | } 133 | 134 | function disable() { 135 | if( device!=null ) { 136 | device = null; 137 | onDisable(this); 138 | } 139 | } 140 | 141 | static function onDevice( p : hxd.Pad ) { 142 | for( i in ALL ){ 143 | if( i.device == null ){ 144 | i.enableDevice( p ); 145 | return; 146 | } 147 | } 148 | 149 | AVAILABLE_DEVICES.push( p ); 150 | p.onDisconnect = function() AVAILABLE_DEVICES.remove( p ); 151 | } 152 | 153 | public function dispose() { 154 | ALL.remove(this); 155 | if( device != null ) 156 | onDevice( device ); 157 | device = null; 158 | } 159 | 160 | public function rumble( strength : Float, time_s : Float ) { 161 | if( isEnabled() ) 162 | device.rumble(strength, time_s); 163 | } 164 | 165 | inline function getControlValue(idx:Int, simplified:Bool, overrideDeadZone:Float=-1.) : Float { 166 | var v = idx > -1 && idxdz?1. : 0.); 174 | else 175 | return v>-dz && v0; 186 | default : return getValue(k,true)!=0; 187 | } 188 | } 189 | 190 | public inline function isDown(k:PadKey) { 191 | return isEnabled() && toggles[k.getIndex()] > 0; 192 | } 193 | 194 | public /*inline */function isPressed(k:PadKey) { 195 | return isEnabled() && toggles[k.getIndex()] == 1; 196 | //var idx = MAPPING[k.getIndex()]; 197 | //var t = isEnabled() && idx>-1 && idx= 1 ) 210 | e.toggles[i] = 2; 211 | else 212 | e.toggles[i] = 1; 213 | } 214 | else{ 215 | e.toggles[i] = 0; 216 | } 217 | } 218 | } 219 | if( hasToggle ) 220 | e.lastActivity = haxe.Timer.stamp(); 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/dn/legacy/HaxeJson.hx: -------------------------------------------------------------------------------- 1 | package dn.legacy; 2 | 3 | import haxe.Serializer; 4 | import haxe.Unserializer; 5 | import haxe.Json; 6 | import Type; 7 | 8 | 9 | // Documentation : Bible MT 10 | 11 | 12 | class HaxeJson { 13 | public var version(default,null) : Int; 14 | 15 | var objVersion : Int; 16 | var obj : Null; 17 | var serialized : Null; 18 | 19 | 20 | public function new(dataVersion:Int) { 21 | version = dataVersion; 22 | objVersion = -1; 23 | } 24 | 25 | 26 | public function serialize(o:Dynamic, ?pretty=false) : Void { 27 | var raw = Json.stringify(o, function(k:Dynamic, v:Dynamic) : Dynamic { 28 | switch( Type.typeof(v) ) { 29 | case TEnum(_) : 30 | return "__hser__" + Serializer.run(v); 31 | 32 | case TClass(List) : 33 | return "__hser__" + Serializer.run(v); 34 | 35 | case TClass(haxe.ds.StringMap), TClass(haxe.ds.IntMap), TClass(haxe.ds.EnumValueMap) : 36 | return "__hser__" + Serializer.run(v); 37 | 38 | case TClass(String), TClass(Array) : 39 | return v; 40 | 41 | case TNull, TInt, TFloat, TBool, TObject : 42 | return v; 43 | 44 | default : 45 | throw k+" has an unsupported type ("+Type.typeof(v)+")"; 46 | } 47 | }); 48 | 49 | raw = '{"version":$version, "data":$raw}'; 50 | 51 | serialized = pretty ? prettify(raw) : raw; 52 | } 53 | 54 | public inline function getSerialized() : String { 55 | if( serialized==null ) 56 | throw 'serialize() must be called first'; 57 | 58 | return serialized; 59 | } 60 | 61 | 62 | 63 | public function unserialize(raw:String) : Void { 64 | var o = Json.parse(raw); 65 | objVersion = Reflect.field(o, "version"); 66 | parseRec(o, function(k:String, v:Dynamic) : Dynamic { 67 | 68 | switch( Type.typeof(v) ) { 69 | 70 | case TClass(String) : 71 | // Rebuild special types 72 | var s : String = cast v; 73 | 74 | // Serialized field 75 | if( s.indexOf("__hser__")==0 ) 76 | return Unserializer.run( s.substr(8) ); 77 | 78 | default : 79 | } 80 | 81 | return v; 82 | 83 | }); 84 | 85 | o = Reflect.field(o, "data"); 86 | 87 | obj = o; 88 | } 89 | 90 | public function getCurrentUnserializedDataVersion() { 91 | return objVersion; 92 | } 93 | 94 | 95 | public function getUnserialized() : Dynamic { 96 | if( version>objVersion ) 97 | throw 'Unserialized object version mismatch, a patch is required (object version:$objVersion, current:$version)'; 98 | 99 | if( versionVoid) { 111 | if( objVersion==from ) { 112 | operation(obj); 113 | objVersion = to; 114 | } 115 | } 116 | 117 | 118 | 119 | static function parseRec(o:Dynamic, replacer:String->Dynamic->Dynamic) { 120 | for(k in Reflect.fields(o)) { 121 | 122 | var v = Reflect.field(o,k); 123 | 124 | Reflect.setField(o, k, replacer(k,v)); 125 | 126 | switch( Type.typeof(v) ) { 127 | 128 | case TObject : 129 | parseRec(v, replacer); 130 | 131 | case TClass(c) : 132 | switch(c) { 133 | 134 | case Array : 135 | // Parse sub array 136 | var arr : Array = cast v; 137 | for(e in arr) 138 | parseRec(e, replacer); 139 | 140 | } 141 | default : 142 | } 143 | 144 | } 145 | } 146 | 147 | 148 | public static function parse(raw:String) { 149 | var hj = new HaxeJson(1); 150 | hj.unserialize(raw); 151 | return hj.getUnserialized(); 152 | } 153 | 154 | public static function stringify(o:Dynamic, ?pretty=false) { 155 | var hj = new HaxeJson(1); 156 | hj.serialize(o, pretty); 157 | return hj.getSerialized(); 158 | } 159 | 160 | 161 | public static function prettify(json:String) { 162 | var strBuff = new StringBuf(); 163 | var inString = false; 164 | var indent = 0; 165 | var jumpBefore : Bool; 166 | var jumpAfter : Bool; 167 | var cid : Int; 168 | 169 | var lastChar : Null = null; 170 | for(c in json.split("")) { 171 | cid = c.charCodeAt(0); 172 | jumpBefore = jumpAfter = false; 173 | if( inString ) { 174 | if( c=="\"" && lastChar!="\\" ) 175 | inString = false; 176 | } 177 | else 178 | switch( c ) { 179 | case " " : 180 | c = ""; 181 | 182 | case "," : 183 | jumpAfter = true; 184 | 185 | case ":" : 186 | c = " : "; 187 | 188 | case "{" : 189 | if( !inString ) { 190 | jumpAfter = true; 191 | indent++; 192 | } 193 | 194 | case "}" : 195 | jumpBefore = true; 196 | indent--; 197 | 198 | case "[" : 199 | if( !inString ) { 200 | jumpAfter = true; 201 | indent++; 202 | } 203 | 204 | case "]" : 205 | jumpBefore = true; 206 | indent--; 207 | 208 | case "\"" : 209 | if( lastChar!="\\" ) 210 | inString = true; 211 | } 212 | if( jumpBefore ) { 213 | strBuff.addChar(10); // new line 214 | for(i in 0...indent) 215 | strBuff.addChar(9); // tab 216 | } 217 | strBuff.addChar(cid); 218 | if( jumpAfter ) { 219 | strBuff.addChar(10); // new line 220 | for(i in 0...indent) 221 | strBuff.addChar(9); // tab 222 | } 223 | lastChar = c; 224 | } 225 | return strBuff.toString(); 226 | } 227 | 228 | } -------------------------------------------------------------------------------- /src/dn/store/StoreAPI.hx: -------------------------------------------------------------------------------- 1 | package dn.store; 2 | 3 | class StoreAPI { 4 | public var name(default,null) : String; 5 | 6 | public function new() { 7 | name = "??"; 8 | } 9 | 10 | /** This must be called before anything else **/ 11 | public function preBootInit() {} 12 | 13 | /** This must be called after the App instance was created **/ 14 | public function appInit() {} 15 | 16 | public function isActive() return false; 17 | 18 | public dynamic function onOverlay(active:Bool) {} 19 | 20 | function print(msg:String) { 21 | Sys.println('$name: $msg'); 22 | } 23 | } -------------------------------------------------------------------------------- /src/dn/store/impl/DummyImpl.hx: -------------------------------------------------------------------------------- 1 | package dn.store.impl; 2 | 3 | class DummyImpl extends dn.store.StoreAPI { 4 | public function new() { 5 | super(); 6 | name = 'Dummy'; 7 | } 8 | 9 | override function isActive() { 10 | return true; 11 | } 12 | } -------------------------------------------------------------------------------- /src/dn/store/impl/SteamImpl.hx: -------------------------------------------------------------------------------- 1 | package dn.store.impl; 2 | 3 | #if !hlsteam 4 | #error "HLSteam is required" 5 | #end 6 | 7 | class SteamImpl extends dn.store.StoreAPI { 8 | var appId : Int; 9 | 10 | public function new(appId) { 11 | super(); 12 | this.appId = appId; 13 | name = 'Steam#$appId'; 14 | } 15 | 16 | override function preBootInit() { 17 | super.preBootInit(); 18 | if( !steam.Api.init(appId) ) 19 | print("Steam init failed."); 20 | } 21 | 22 | override function isActive() { 23 | return steam.Api.active; 24 | } 25 | 26 | override function appInit() { 27 | super.appInit(); 28 | 29 | // Check Steam 30 | if( !isActive() ) { 31 | #if !debug 32 | throw "Steam is not running!"; 33 | #end 34 | } 35 | 36 | #if debug 37 | print("Steam ready."); 38 | #end 39 | 40 | steam.Api.onOverlay = onOverlay; 41 | 42 | } 43 | } -------------------------------------------------------------------------------- /src/dn/struct/Grid.hx: -------------------------------------------------------------------------------- 1 | package dn.struct; 2 | 3 | /** 4 | A 2D Grid structure. It cannot be resized after creation. 5 | **/ 6 | @:allow(dn.struct.GridIterator) 7 | class Grid { 8 | public var wid(default,null) : Int; 9 | public var hei(default,null) : Int; 10 | 11 | var data : Map; 12 | 13 | public function new(w,h) { 14 | wid = w; 15 | hei = h; 16 | data = new Map(); 17 | } 18 | 19 | @:keep 20 | public inline function toString() { 21 | return 'Grid(${wid}x$hei)'; 22 | } 23 | 24 | /** Return a text representation of the grid, for debug purpose. **/ 25 | public function debug() { 26 | var lines = []; 27 | for(cy in 0...hei) { 28 | var l = ""; 29 | for(cx in 0...wid) { 30 | if( !hasValue(cx,cy) ) 31 | l+=".. "; 32 | else { 33 | l += Std.string( get(cx,cy) ); 34 | while( l.length<3 ) l+=" "; 35 | } 36 | } 37 | lines.push(l); 38 | } 39 | 40 | return lines.join("\n"); 41 | } 42 | 43 | public inline function iterator() return new GridIterator(this); 44 | 45 | public function dispose(?elementDisposer:T->Void) { 46 | if( elementDisposer!=null ) 47 | for(e in this) 48 | elementDisposer(e); 49 | data = null; 50 | } 51 | 52 | /** Return TRUE if given coordinate is in grid bounds **/ 53 | public inline function isValid(x:Int, y:Int) { 54 | return x>=0 && x=0 && y { 69 | return isValid(x,y) ? data.get( coordId(x,y) ) : null; 70 | } 71 | 72 | /** Return TRUE if given grid cell contains a non-null value. **/ 73 | public inline function hasValue(x:Int, y:Int) : Bool { 74 | return isValid(x,y) && data.get( coordId(x,y) )!=null; 75 | } 76 | 77 | /** Remove the value of a grid cell. **/ 78 | public inline function remove(x:Int, y:Int) { 79 | if( isValid(x,y) ) 80 | data.remove( coordId(x,y) ); 81 | } 82 | 83 | /** Fill all the grid with given value. **/ 84 | public inline function fill(v:T) { 85 | for(x in 0...wid) 86 | for(y in 0...hei) 87 | data.set( coordId(x,y), v ); 88 | } 89 | 90 | /** Remove all grid values. **/ 91 | public inline function empty() { 92 | data = new Map(); 93 | } 94 | } 95 | 96 | 97 | 98 | /** 99 | Custom iterator for Grid 100 | **/ 101 | private class GridIterator { 102 | var grid : Grid; 103 | var i : Int; 104 | 105 | 106 | public inline function new(grid:Grid) { 107 | this.grid = grid; 108 | i = 0; 109 | } 110 | 111 | public inline function hasNext() { 112 | return i < grid.wid * grid.hei; 113 | } 114 | 115 | public inline function next() { 116 | return grid.data.get(i++); 117 | } 118 | } -------------------------------------------------------------------------------- /src/dn/struct/RandDeck.hx: -------------------------------------------------------------------------------- 1 | package dn.struct; 2 | import haxe.ds.Vector; 3 | 4 | class RandDeck { 5 | public var size(default, null) : Int; 6 | var elements : Null< Vector >; 7 | var curIdx : Int; 8 | var rndFunc : Int->Int; 9 | public var autoShuffle = true; 10 | 11 | public function new(?rnd : Int->Int, ?values:Array) { 12 | curIdx = 0; 13 | size = 0; 14 | rndFunc = rnd==null ? Std.random : rnd; 15 | if( values!=null ) 16 | for(v in values) 17 | push(v); 18 | } 19 | 20 | public function toString() { 21 | return 'RandDeck($size)=>'+getAllElements(); 22 | } 23 | 24 | function grow(newSize:Int) { 25 | var oldSize = elements==null ? 0 : elements.length; 26 | 27 | var old = elements; 28 | elements = new Vector(newSize*2); 29 | if( old!=null && oldSize>0 ) 30 | Vector.blit(old, 0, elements, 0, oldSize); 31 | } 32 | 33 | public function push(v:T, n=1) { 34 | var newSize = size + n; 35 | if( elements==null || newSize >= elements.length ) 36 | grow(newSize); 37 | 38 | for( i in size...newSize ) 39 | elements[i] = v; 40 | size = newSize; 41 | } 42 | 43 | public inline function shuffle() { 44 | curIdx = 0; 45 | for (i in 0...(size-1)) { 46 | var j = rndFunc(size - i); 47 | var tmp = elements[i]; 48 | elements[i] = elements[i + j]; 49 | elements[i + j] = tmp; 50 | } 51 | } 52 | 53 | public inline function countRemaining() return size-curIdx; 54 | 55 | public function clear() { 56 | curIdx = 0; 57 | size = 0; 58 | } 59 | 60 | public function draw() : Null { 61 | if( size==0 ) 62 | return null; 63 | 64 | var v = elements[curIdx]; 65 | if( ++curIdx >= size && autoShuffle ) 66 | shuffle(); 67 | 68 | return v; 69 | } 70 | 71 | public inline function peekNextDraw() : Null { 72 | return elements[curIdx]; 73 | } 74 | 75 | public function getRemainingElements() : Array { 76 | var a = []; 77 | for(i in curIdx...size) 78 | a.push( elements[i] ); 79 | return a; 80 | } 81 | 82 | public function getAllElements() : Array { 83 | var a = []; 84 | for(i in 0...size) 85 | a.push( elements[i] ); 86 | return a; 87 | } 88 | 89 | 90 | #if deepnightLibsTests 91 | public static function test() { 92 | var randDeck = new RandDeck(); 93 | randDeck.push(0, 2); 94 | randDeck.push(1, 5); 95 | randDeck.push(2, 10); 96 | randDeck.shuffle(); 97 | CiAssert.isTrue( randDeck.countRemaining()==17 ); 98 | CiAssert.noException("RandDeck.draw() check", { 99 | for(i in 0...200) { 100 | var v = randDeck.draw(); 101 | if( v==null || v<0 || v>2 ) 102 | throw "Value "+v+" is incorrect"; 103 | } 104 | }); 105 | } 106 | #end 107 | } -------------------------------------------------------------------------------- /src/dn/struct/RandList.hx: -------------------------------------------------------------------------------- 1 | package dn.struct; 2 | 3 | class RandList { 4 | public var totalProba(default, null) : Int; 5 | 6 | var drawList : Array<{proba:Int, value:T}>; 7 | var defaultRandom : Int->Int; 8 | public var length(get,never) : Int; inline function get_length() return drawList.length; 9 | 10 | public function new(?rndFunc:Int->Int, ?arr:Array) { 11 | if( rndFunc!=null ) 12 | defaultRandom = rndFunc; 13 | else 14 | defaultRandom = Std.random; 15 | 16 | totalProba = 0; 17 | drawList = new Array(); 18 | 19 | if( arr != null ) 20 | addArray(arr); 21 | } 22 | 23 | public function clear() { 24 | totalProba = 0; 25 | drawList = []; 26 | } 27 | 28 | public function setProba(v:T, p:Int) { 29 | for(e in drawList) 30 | if( e.value==v ) { 31 | totalProba-=e.proba; 32 | e.proba = p; 33 | totalProba+=e.proba; 34 | } 35 | } 36 | 37 | // Crée une nouvelle RandList en ne conservant que certains éléments 38 | public function filter(keep:T->Bool) : RandList { 39 | var out = new RandList(defaultRandom); 40 | for(e in drawList) 41 | if( keep(e.value) ) 42 | out.add(e.value, e.proba); 43 | return out; 44 | } 45 | 46 | // Crée une RandList en utilisant les metadata d'un enum comme proba 47 | public static function fromEnum( e : Enum, ?metaFieldName = "proba" ) : RandList { 48 | var n = Type.getEnumName(e); 49 | var r = new RandList(); 50 | var meta = haxe.rtti.Meta.getFields(e); 51 | 52 | for ( k in Type.getEnumConstructs(e) ) { 53 | var p = Type.createEnum(e, k); 54 | r.add( p, Reflect.field( Reflect.field(meta,k), metaFieldName )[0] ); 55 | } 56 | 57 | return r; 58 | } 59 | 60 | 61 | #if flash 62 | public static function fromMap(m:Map) : RandList { 63 | var r = new RandList(); 64 | for(k in m.keys()) 65 | r.add(k, m.get(k)); 66 | return r; 67 | } 68 | #end 69 | 70 | public function add(elem:T, ?proba=1) { 71 | if( proba<=0 ) 72 | return this; 73 | 74 | // Add to existing if this elem is already there 75 | for(e in drawList) 76 | if( e.value==elem ) { 77 | e.proba+=proba; 78 | totalProba+=proba; 79 | return this; 80 | } 81 | 82 | drawList.push( { proba:proba, value:elem } ); 83 | totalProba += proba; 84 | 85 | return this; 86 | } 87 | 88 | public function addArray(arr:Array, ?proba=1) { 89 | for(i in 0...arr.length) { 90 | var e = arr[i]; 91 | if( contains(e) ) 92 | continue; 93 | 94 | var n = 1; 95 | for(j in i+1...arr.length) 96 | if( arr[j]==e ) 97 | n++; 98 | 99 | add(e, n*proba); 100 | } 101 | } 102 | 103 | public function contains(search:T) { 104 | for(e in drawList) 105 | if( e.value==search ) 106 | return true; 107 | 108 | return false; 109 | } 110 | 111 | public function remove(search:T) { 112 | totalProba = 0; 113 | 114 | var i = 0; 115 | while( iInt) : Null { 127 | var n = rndFunc==null ? defaultRandom(totalProba) : rndFunc(totalProba); 128 | 129 | var accu = 0; 130 | for (e in drawList) { 131 | if ( n < accu + e.proba ) 132 | return e.value; 133 | 134 | accu += e.proba; 135 | } 136 | 137 | return null; 138 | } 139 | 140 | public function filteredDraw(filter:T->Bool, ?rndFunc:Int->Int) : Null { 141 | var fList = drawList.filter(function(e) return filter(e.value)); 142 | var total = 0; 143 | for(e in fList) 144 | total+=e.proba; 145 | 146 | var n = rndFunc==null ? defaultRandom(total) : rndFunc(total); 147 | 148 | var accu = 0; 149 | for (e in fList) { 150 | if ( n < accu + e.proba ) 151 | return e.value; 152 | 153 | accu += e.proba; 154 | } 155 | 156 | return null; 157 | } 158 | 159 | public inline function getValues() { 160 | return drawList.map( function(e) return e.value ); 161 | } 162 | 163 | public function getProba(v:T) { 164 | for(e in drawList) 165 | if( e.value==v ) 166 | return e.proba; 167 | return 0; 168 | } 169 | 170 | public inline function getProbaPct(v:T) : Float { // 0-100 171 | return totalProba==0 ? 0 : Math.round(1000*getProba(v)/totalProba) / 10; 172 | } 173 | 174 | public inline function isEmpty() return drawList.length==0; 175 | 176 | public function toString() { 177 | var list = new List(); 178 | 179 | for (e in drawList) 180 | list.add(Std.string(e.value) + " => " + ( totalProba==0 ? 0 : Math.round(1000 * e.proba / totalProba) / 10 ) + "%"); 181 | 182 | return list.join(" | "); 183 | } 184 | 185 | public function getDebugArray() { 186 | var a = new Array(); 187 | 188 | for (e in drawList) 189 | a.push( { 190 | v : e.value, 191 | pct : ( totalProba==0 ? 0 : Math.round(1000 * e.proba / totalProba) / 10 ) 192 | } ); 193 | 194 | return a; 195 | } 196 | 197 | 198 | #if deepnightLibsTests 199 | public static function test() { 200 | var randList = new RandList([0,1,2,3]); 201 | CiAssert.noException("RandList.draw() check", { 202 | for(i in 0...200) { 203 | var v = randList.draw(); 204 | if( v==null || v<0 || v>3 ) 205 | throw "Value "+v+" is incorrect"; 206 | } 207 | }); 208 | CiAssert.isTrue( randList.getProbaPct(2)==25 ); 209 | CiAssert.isTrue( randList.contains(3) ); 210 | } 211 | #end 212 | } 213 | -------------------------------------------------------------------------------- /src/dn/struct/Stat.hx: -------------------------------------------------------------------------------- 1 | package dn.struct; 2 | 3 | /** 4 | A simple class to manage a value within bounds. 5 | Typically useful to store some game value, like player HP. 6 | **/ 7 | @:generic 8 | class Stat { 9 | /** Current value, clamped between `min` and `max` **/ 10 | public var v(default,set) : T; 11 | 12 | public var min(default,set) : T; 13 | public var max(default,set) : T; 14 | 15 | /** Ratio of the current "maxed" ratio (0=value is zero, to 1=value is maxed) **/ 16 | public var fullRatio(get,never) : Float; 17 | 18 | /** Ratio of the current "emptied" ratio (0=value is maxed, to 1=value is zero) **/ 19 | public var emptyRatio(get,never) : Float; 20 | 21 | /** Same as `fullRatio` but with 2 digits max **/ 22 | public var prettyFullRatio(get,never) : Float; 23 | 24 | var zero : T = cast 0; 25 | 26 | /** Callback when `v` changes **/ 27 | public var onChange : Null< Void->Void >; 28 | 29 | public function new() { 30 | init(zero,zero,zero); 31 | } 32 | 33 | @:keep public function toString() { 34 | return min==zero ? '$v/$max' : '[$min]$v/$max'; 35 | } 36 | 37 | public inline function clone() : Stat { 38 | var s = new Stat(); 39 | s.init(v,min,max); 40 | s.onChange = onChange; 41 | return s; 42 | } 43 | 44 | public inline function isZero() return v==zero; 45 | public inline function isMin() return v==min; 46 | public inline function isMax() return v==max; 47 | public inline function isMinOrMax() return v==min || v==max; 48 | 49 | public inline function setBounds(min:T, max:T) { 50 | this.min = min; 51 | this.max = max; 52 | v = clamp(v); 53 | } 54 | 55 | /** Set stat value using either: `set(value,max)` or `set(value,min,max)` **/ 56 | public inline function init(value:T, maxOrMin:T, ?max:T) { 57 | if( max==null ) { 58 | this.max = maxOrMin; 59 | this.v = value; 60 | } 61 | else { 62 | this.min = maxOrMin; 63 | this.max = max; 64 | this.v = value; 65 | } 66 | } 67 | 68 | /** Customize the `max`, and set `min` and `v` to 0 **/ 69 | public inline function initZeroOnMax(max:T) { 70 | init(zero, zero,max); 71 | } 72 | 73 | /** Set both `v` and `max` to `value`, and set `min` to 0 **/ 74 | public inline function initMaxOnMax(value:T) { 75 | init(value, zero,value); 76 | } 77 | 78 | public inline function empty() { 79 | v = min; 80 | } 81 | 82 | public inline function maxOut() { 83 | v = max; 84 | } 85 | 86 | inline function clamp(value:T) : T { 87 | return 88 | valuemax ? max : 90 | value; 91 | } 92 | 93 | inline function set_v(value:T) : T{ 94 | if( onChange==null ) 95 | v = clamp(value); 96 | else { 97 | var old = v; 98 | v = clamp(value); 99 | if( old!=v ) 100 | onChange(); 101 | } 102 | return v; 103 | } 104 | 105 | inline function set_max(value:T) : T { 106 | max = value; 107 | if( min>max ) 108 | min = max; 109 | v = clamp(v); 110 | return max; 111 | } 112 | 113 | inline function set_min(value:T) : T { 114 | min = value; 115 | if( max = new Stat(); 140 | s.initZeroOnMax(3); 141 | CiAssert.equals( Type.typeof(s.v), Type.ValueType.TInt ); 142 | CiAssert.equals( s.v, 0 ); 143 | CiAssert.equals( s.max, 3 ); 144 | 145 | // v clamp 146 | CiAssert.equals( --s.v, 0 ); 147 | CiAssert.equals( ++s.v, 1 ); 148 | CiAssert.equals( ++s.v, 2 ); 149 | CiAssert.equals( ++s.v, 3 ); 150 | CiAssert.equals( ++s.v, 3 ); 151 | 152 | // Max change 153 | CiAssert.equals( { s.max--; s.toString(); }, "2/2" ); 154 | CiAssert.equals( { s.max--; s.toString(); }, "1/1" ); 155 | CiAssert.equals( { s.max++; s.toString(); }, "1/2" ); 156 | CiAssert.equals( { s.max++; s.toString(); }, "1/3" ); 157 | 158 | // Min change 159 | CiAssert.equals( { s.min=1; s.toString(); }, "[1]1/3" ); 160 | CiAssert.equals( { s.min=2; s.toString(); }, "[2]2/3" ); 161 | CiAssert.equals( { s.min=3; s.toString(); }, "[3]3/3" ); 162 | 163 | // Ratio 164 | s.initZeroOnMax(2); 165 | CiAssert.equals( s.toString(), "0/2" ); 166 | CiAssert.equals( { s.v=0; s.fullRatio; }, 0 ); 167 | CiAssert.equals( { s.v=1; s.fullRatio; }, 0.5 ); 168 | CiAssert.equals( { s.v=2; s.fullRatio; }, 1 ); 169 | CiAssert.equals( { s.v=5; s.fullRatio; }, 1 ); 170 | 171 | CiAssert.equals( { s.v=0; s.emptyRatio; }, 1 ); 172 | CiAssert.equals( { s.v=1; s.emptyRatio; }, 0.5 ); 173 | CiAssert.equals( { s.v=2; s.emptyRatio; }, 0 ); 174 | CiAssert.equals( { s.v=5; s.emptyRatio; }, 0 ); 175 | 176 | // Ratio with min 177 | s.init(0, 1,3); 178 | CiAssert.equals( s.toString(), "[1]1/3" ); 179 | CiAssert.equals( { s.v=1; s.fullRatio; }, 0 ); 180 | CiAssert.equals( { s.v=2; s.fullRatio; }, 0.5 ); 181 | CiAssert.equals( { s.v=3; s.fullRatio; }, 1 ); 182 | 183 | CiAssert.equals( { s.v=1; s.emptyRatio; }, 1 ); 184 | CiAssert.equals( { s.v=2; s.emptyRatio; }, 0.5 ); 185 | CiAssert.equals( { s.v=3; s.emptyRatio; }, 0 ); 186 | 187 | // Negative min 188 | s.init(0, -1,3); 189 | CiAssert.equals( s.toString(), "[-1]0/3" ); 190 | CiAssert.equals( { s.v--; s.toString(); }, "[-1]-1/3" ); 191 | CiAssert.equals( { s.v--; s.toString(); }, "[-1]-1/3" ); 192 | CiAssert.equals( { s.min=0; s.toString(); }, "0/3" ); 193 | 194 | // Pretty ratio 195 | s.initZeroOnMax(3); 196 | CiAssert.equals( s.toString(), "0/3" ); 197 | CiAssert.equals( { s.v=1; s.toString(); }, "1/3" ); 198 | CiAssert.equals( s.prettyFullRatio, 0.33 ); 199 | 200 | // Weird min/max changes 201 | s.initZeroOnMax(3); 202 | CiAssert.equals( s.toString(), "0/3" ); 203 | CiAssert.equals( { s.min=5; s.toString(); }, "[5]5/5" ); 204 | CiAssert.equals( { s.max=2; s.toString(); }, "[2]2/2" ); 205 | CiAssert.equals( { s.max=3; s.toString(); }, "[2]2/3" ); 206 | CiAssert.equals( { s.min=1; s.toString(); }, "[1]2/3" ); 207 | CiAssert.equals( { s.min=0; s.toString(); }, "2/3" ); 208 | CiAssert.equals( { s.max=-1; s.toString(); }, "[-1]-1/-1" ); 209 | CiAssert.equals( { s.max=2; s.toString(); }, "[-1]-1/2" ); 210 | CiAssert.equals( { s.min=0; s.toString(); }, "0/2" ); 211 | CiAssert.equals( { s.min=s.max*2; s.toString(); }, "[4]4/4" ); 212 | CiAssert.equals( { s.max=-s.min; s.toString(); }, "[-4]-4/-4" ); 213 | 214 | // Cloning 215 | var c = s.clone(); 216 | CiAssert.equals( c.v, s.v ); 217 | CiAssert.equals( c.min, s.min ); 218 | CiAssert.equals( c.max, s.max ); 219 | } 220 | } 221 | #end -------------------------------------------------------------------------------- /steam.tests.hl.hxml: -------------------------------------------------------------------------------- 1 | tests.base.hxml 2 | -lib hlsteam 3 | 4 | -hl tests/bin/test.hl 5 | --cmd hl tests/bin/test.hl -------------------------------------------------------------------------------- /tests.base.hxml: -------------------------------------------------------------------------------- 1 | -cp src 2 | -cp tests 3 | -lib castle 4 | --resource res/cdbTest.cdb@cdbTest 5 | --resource res/lang/legacy_fr.mo@legacy_fr 6 | --resource res/lang/new_fr.po@new_fr 7 | -D deepnightLibsTests 8 | 9 | -main Tests 10 | -------------------------------------------------------------------------------- /tests.hl.hxml: -------------------------------------------------------------------------------- 1 | tests.base.hxml 2 | -hl tests/bin/test.hl 3 | --cmd hl tests/bin/test.hl -------------------------------------------------------------------------------- /tests.js.hxml: -------------------------------------------------------------------------------- 1 | tests.base.hxml 2 | -js tests/bin/test.js 3 | -lib hxnodejs 4 | --cmd node tests/bin/test.js 5 | -------------------------------------------------------------------------------- /tests/CdbTest.hx: -------------------------------------------------------------------------------- 1 | private typedef Init = haxe.macro.MacroType<[cdb.Module.build("cdbTest.cdb")]>; 2 | -------------------------------------------------------------------------------- /tests/LangParser.hx: -------------------------------------------------------------------------------- 1 | class LangParser { 2 | public static function main() { 3 | // Legacy GetText 4 | var name = "legacy_sourceTexts"; 5 | Sys.println("Building "+name+" file..."); 6 | var cdbs = findAll("res", "cdb"); 7 | try { 8 | var data = dn.legacy.GetText.doParseGlobal({ 9 | codePath: "src", 10 | codeIgnore: null, 11 | cdbFiles: cdbs, 12 | cdbSpecialId: [], 13 | potFile: "res/lang/"+name+".pot", 14 | }); 15 | } 16 | catch(e:String) { 17 | Sys.println(""); 18 | Sys.println(e); 19 | Sys.println("Extraction failed: fatal error!"); 20 | Sys.println(""); 21 | Sys.exit(1); 22 | } 23 | 24 | // New GetText 25 | var all = dn.data.GetText.parseSourceCode("src"); 26 | all = all.concat( dn.data.GetText.parseCastleDB("res/cdbTest.cdb") ); 27 | dn.data.GetText.writePOT("res/lang/new_sourceTexts.pot", all); 28 | Sys.println("Done."); 29 | } 30 | 31 | static function findAll(path:String, ext:String, ?cur:Array) { 32 | var ext = "."+ext; 33 | var all = cur==null ? [] : cur; 34 | for(e in sys.FileSystem.readDirectory(path)) { 35 | e = path+"/"+e; 36 | if( e.indexOf(ext)>=0 && e.lastIndexOf(ext)==e.length-ext.length ) 37 | all.push(e); 38 | if( sys.FileSystem.isDirectory(e) && e.indexOf(".tmp")<0 ) 39 | findAll(e, ext, all); 40 | } 41 | return all; 42 | } 43 | } -------------------------------------------------------------------------------- /tests/Tests.hx: -------------------------------------------------------------------------------- 1 | import dn.CiAssert; 2 | import dn.Lib; 3 | 4 | class Tests { 5 | #if !macro 6 | public static function main() { 7 | #if verbose 8 | CiAssert.VERBOSE = true; 9 | #end 10 | 11 | // Assert itself 12 | CiAssert.isTrue(true); 13 | CiAssert.isFalse(false); 14 | CiAssert.isNotNull("a"); 15 | CiAssert.equals("a","a"); 16 | 17 | // Libs 18 | dn.Args.test(); 19 | dn.pathfinder.AStar.test(); 20 | dn.geom.Bresenham.test(); 21 | dn.geom.Geom.test(); 22 | dn.Changelog.test(); 23 | dn.Cinematic.test(); 24 | dn.Col.UnitTest.test(); 25 | dn.legacy.Color.test(); 26 | dn.Cooldown.test(); 27 | dn.DecisionHelper.test(); 28 | dn.Delayer.test(); 29 | dn.FilePath.test(); 30 | dn.struct.FixedArray.FixedArrayTests.test(); 31 | dn.data.GetText.test(); 32 | dn.legacy.GetText.test(); 33 | dn.Lib.test(); 34 | dn.data.LocalStorage.test(); 35 | dn.M.test(); 36 | dn.Process.test(); 37 | dn.Rand.test(); 38 | dn.struct.RandDeck.test(); 39 | dn.struct.RandList.test(); 40 | dn.RandomTools.test(); 41 | dn.struct.RecyclablePool.test(); 42 | dn.struct.Stat.StatTest.test(); 43 | dn.phys.Velocity.test(); 44 | dn.Version.test(); 45 | dn.Log.test(); 46 | dn.TinyTween.test(); 47 | 48 | 49 | 50 | // Done! 51 | Lib.println(""); 52 | Lib.println("Tests succcessfully completed!"); 53 | } 54 | #end 55 | } --------------------------------------------------------------------------------