├── .gitignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── LICENSE ├── README.md ├── Run.hx ├── common.hxml ├── demo ├── assets │ ├── agent.js │ └── bullet.js ├── pre-publish │ └── license-comment.js └── src │ ├── BulletPatterns.hx │ ├── Dom.hx │ ├── Emitter.hx │ ├── EventHandler.hx │ ├── Global.hx │ ├── Main.hx │ ├── World.hx │ ├── actor │ ├── Actor.hx │ ├── ActorAosoa.hx │ ├── Army.hx │ ├── ArmyBuilder.hx │ ├── Constants.hx │ └── import.hx │ ├── import.hx │ └── scenes │ └── PlayScene.hx ├── haxelib.json ├── hl-jit-test.hxml ├── hl-jit.hxml ├── hxformat.json ├── js.hxml ├── res ├── agent.png └── bullet.png └── src └── firedancer ├── assembly ├── Assembler.hx ├── AssemblyCode.hx ├── AssemblyCodePackage.hx ├── Builder.hx ├── DataRegisterSpecifier.hx ├── Instruction.hx ├── InstructionAssembler.hx ├── InstructionExtension.hx ├── InstructionOptimizer.hx ├── Operand.hx ├── OperandKind.hx ├── OperandTools.hx ├── Optimizer.hx ├── ValueType.hx ├── Word.hx ├── WordArray.hx └── types │ ├── ActorProperty.hx │ ├── ActorPropertyComponent.hx │ ├── ActorPropertyExtension.hx │ ├── ActorPropertyType.hx │ ├── CastOperationType.hx │ ├── EventType.hx │ ├── FireType.hx │ └── TargetProperty.hx ├── import.hx ├── script ├── Api.hx ├── ApiEx.hx ├── Ast.hx ├── AstNode.hx ├── CompileContext.hx ├── api_components │ ├── ActorPropertyApiComponent.hx │ ├── Let.hx │ ├── Position.hx │ ├── Random.hx │ ├── Shot.hx │ ├── ShotPosition.hx │ ├── ShotVelocity.hx │ ├── Velocity.hx │ └── import.hx ├── expression │ ├── AngleExpression.hx │ ├── AngleLocalVariableExpression.hx │ ├── ExpressionData.hx │ ├── FloatExpression.hx │ ├── FloatLikeExpressionData.hx │ ├── FloatLocalVariableExpression.hx │ ├── GenericExpression.hx │ ├── IntExpression.hx │ ├── IntLikeExpressionData.hx │ ├── IntLocalVariableExpression.hx │ ├── README.md │ ├── Transformation.hx │ ├── VecExpression.hx │ ├── VecExpressionData.hx │ ├── import.hx │ └── subtypes │ │ ├── FloatLikeConstant.hx │ │ ├── FloatLikeConstantData.hx │ │ ├── FloatLikeRuntimeExpression.hx │ │ ├── IntLikeConstant.hx │ │ ├── IntLikeRuntimeExpression.hx │ │ └── RuntimeExpressionEnum.hx └── nodes │ ├── AddActorProperty.hx │ ├── Aim.hx │ ├── Async.hx │ ├── Comment.hx │ ├── Debug.hx │ ├── DeclareLocalVariable.hx │ ├── Duplicate.hx │ ├── EachFrame.hx │ ├── End.hx │ ├── Event.hx │ ├── Fire.hx │ ├── List.hx │ ├── Loop.hx │ ├── OperateLocalVariable.hx │ ├── Parallel.hx │ ├── Repeat.hx │ ├── SetActorProperty.hx │ ├── Wait.hx │ └── import.hx └── types ├── Angle.hx └── Azimuth.hx /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.VSCodeCounter/ 3 | /web/ 4 | /out/ 5 | /dump/ 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Test HashLink (HL/JIT)", 6 | "request": "launch", 7 | "type": "hl", 8 | "cwd": "${workspaceRoot}", 9 | "preLaunchTask": "Build" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "editor.insertSpaces": false, 4 | "files.eol": "\n", 5 | "files.trimTrailingWhitespace": true, 6 | "files.trimFinalNewlines": true, 7 | "files.insertFinalNewline": true, 8 | "codedox": { 9 | "commentbegin": "/**", 10 | "commentprefix": "\t", 11 | "commentend": "**/", 12 | "autoPrefixOnEnter": false 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Build and Test", 6 | "type": "hxml", 7 | "file": "hl-jit-test.hxml", 8 | "problemMatcher": [ 9 | "$haxe-absolute", 10 | "$haxe", 11 | "$haxe-error", 12 | "$haxe-trace" 13 | ], 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | }, 19 | { 20 | "label": "Build", 21 | "type": "hxml", 22 | "file": "hl-jit.hxml", 23 | "problemMatcher": [ 24 | "$haxe-absolute", 25 | "$haxe", 26 | "$haxe-error", 27 | "$haxe-trace" 28 | ], 29 | "group": "build" 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 FAL Works 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 | # Firedancer 2 | 3 | A [Haxe](https://haxe.org)-based language for defining 2D shmups bullet-hell patterns. 4 | 5 | ![firedancer_v0_1_0](https://user-images.githubusercontent.com/33595446/91944104-8398c480-ed38-11ea-927e-f0f107977e98.gif) 6 | 7 | You can write any bullet pattern in Haxe and compile it into bytecode programs, 8 | which can be run on [Firedancer VM](https://github.com/fal-works/firedancer-vm). 9 | 10 | Inspired by [BulletML](http://www.asahi-net.or.jp/~cs8k-cyu/bulletml/index_e.html). 11 | 12 | Requires **Haxe 4** (developed with v4.1.3). 13 | 14 | 15 | ## Demo 16 | 17 | **[firedancer-lang.com](https://firedancer-lang.com)** 18 | 19 | (Repository for the website: [firedancer-web](https://github.com/fal-works/firedancer-web)) 20 | 21 | 22 | ## Usage 23 | 24 | ### Install 25 | 26 | First install [Haxe 4](https://haxe.org). 27 | 28 | Then install Firedancer: 29 | 30 | ```sh 31 | haxelib install firedancer 32 | ``` 33 | 34 | ### Create bullet patterns 35 | 36 | Import top-level properties and functions in your Haxe project: 37 | 38 | ```haxe 39 | import firedancer.script.Api.*; 40 | import firedancer.script.ApiEx.*; 41 | ``` 42 | 43 | Using that API of Firedancer (see also [Wiki](https://github.com/fal-works/firedancer/wiki)), 44 | define your own bullet patterns and compile them into a `ProgramPackage`. 45 | 46 | ### Run 47 | 48 | Attach any `Program` instance(s) to your actor(s) and run them on [Firedancer VM](https://github.com/fal-works/firedancer-vm). 49 | 50 | *Note: Firedancer is not a game engine. You have to prepare your own program to create/update/render your actors.* 51 | 52 | 53 | ## Example Project 54 | 55 | Here is a minimum Haxe project using Firedancer and [Heaps.io](https://heaps.io): 56 | 57 | [Firedancer Template (with Heaps.io)](https://github.com/fal-works/firedancer-heaps-template) 58 | 59 | 60 | ## Documentation 61 | 62 | [Firedancer Wiki](https://github.com/fal-works/firedancer/wiki) 63 | 64 | 65 | ## Caveats 66 | 67 | - It's still alpha and quite unstable. Everything may change in future. 68 | - A bunch of spaghetti code!! Much more to be refactored/optimized. 69 | 70 | 71 | ## Dependencies 72 | 73 | - [sinker](https://github.com/fal-works/sinker) v0.5.0 or compatible 74 | - [sneaker](https://github.com/fal-works/sneaker) v0.11.0 or compatible 75 | - [ripper](https://github.com/fal-works/ripper) v0.4.0 or compatible 76 | - [banker](https://github.com/fal-works/banker) v0.7.1 or compatible 77 | - [reckoner](https://github.com/fal-works/banker) v0.2.0 or compatible 78 | - [firedancer-vm](https://github.com/fal-works/firedancer-vm) v0.1.0 or compatible 79 | -------------------------------------------------------------------------------- /Run.hx: -------------------------------------------------------------------------------- 1 | class Run { 2 | static function main() { 3 | #if sys 4 | final libraryName: String = "firedancer"; 5 | final version = haxe.macro.Compiler.getDefine("firedancer"); 6 | 7 | final url = 'https://lib.haxe.org/p/${libraryName}/'; 8 | 9 | Sys.println('\n${libraryName} ${version}\n${url}\n'); 10 | #end 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /common.hxml: -------------------------------------------------------------------------------- 1 | # Common Build arguments 2 | 3 | --class-path demo/src 4 | --main Main 5 | 6 | --dce full 7 | --macro nullSafety("firedancer", Strict) 8 | 9 | -lib broker 10 | -lib reckoner 11 | -lib firedancer-vm 12 | -lib firedancer 13 | 14 | -lib heaps 15 | 16 | -D windowTitle=Firedancer 17 | 18 | -D sneaker_assertion_disable 19 | -D broker_catch_disable 20 | -D firedancer_positionref_type=broker_BatchSprite 21 | 22 | # --debug 23 | -------------------------------------------------------------------------------- /demo/assets/agent.js: -------------------------------------------------------------------------------- 1 | function setup() { 2 | createCanvas(100, 100); 3 | 4 | const g = createGraphics(48, 48); 5 | g.fill("#4d089a20"); 6 | g.stroke("#4d089a"); 7 | g.strokeWeight(2); 8 | g.rect(4, 4, 40, 40); 9 | 10 | image(g, 0, 0); 11 | save(g, "agent.png"); 12 | } 13 | -------------------------------------------------------------------------------- /demo/assets/bullet.js: -------------------------------------------------------------------------------- 1 | function setup() { 2 | createCanvas(100, 100); 3 | 4 | const g = createGraphics(16, 16); 5 | g.fill("#4d089a20"); 6 | g.stroke("#4d089a"); 7 | g.strokeWeight(1); 8 | g.rect(1, 1, 14, 14); 9 | 10 | image(g, 0, 0); 11 | save(g, "bullet.png"); 12 | } 13 | -------------------------------------------------------------------------------- /demo/pre-publish/license-comment.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Firedancer Demo 3 | * https://github.com/fal-works/firedancer 4 | * 5 | * @copyright 2020 FAL Works 6 | * @license MIT 7 | */ 8 | /** 9 | * The following software may be included in this product: 10 | * 11 | * The Haxe Standard Library 12 | * https://haxe.org 13 | * © 2005-2016 Haxe Foundation, 14 | * used under MIT License (https://haxe.org/foundation/open-source.html) 15 | */ 16 | -------------------------------------------------------------------------------- /demo/src/Dom.hx: -------------------------------------------------------------------------------- 1 | #if js 2 | import js.Browser.document; 3 | #end 4 | 5 | class Dom { 6 | public static function initialize() { 7 | #if js 8 | final selector: js.html.SelectElement = cast document.getElementById("demo-selector"); 9 | selector.onchange = () -> { 10 | if (Global.world.isNone()) return; 11 | 12 | final pkg = BulletPatterns.get(selector.value); 13 | if (pkg.isNone()) return; 14 | final programPackage = pkg.unwrap(); 15 | 16 | Global.world.unwrap() 17 | .reset( 18 | programPackage, 19 | programPackage.getProgramByName("main") 20 | ); 21 | }; 22 | #end 23 | } 24 | 25 | public static function script(text: String) { 26 | #if js 27 | document.getElementById("script").textContent = text; 28 | #end 29 | } 30 | 31 | public static function assembly(text: String) { 32 | #if js 33 | document.getElementById("assembly").textContent = text; 34 | #end 35 | } 36 | 37 | public static function program(text: String) { 38 | #if js 39 | document.getElementById("program").textContent = text; 40 | #end 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /demo/src/Emitter.hx: -------------------------------------------------------------------------------- 1 | import firedancer.vm.Program; 2 | import firedancer.vm.PositionRef; 3 | import actor.ActorAosoa; 4 | 5 | /** 6 | Object for emitting new bullet. 7 | **/ 8 | class Emitter extends firedancer.vm.Emitter { 9 | var aosoa: ActorAosoa; 10 | 11 | public function new() 12 | this.aosoa = @:nullSafety(Off) null; 13 | 14 | public function initialize(aosoa: ActorAosoa): Void 15 | this.aosoa = aosoa; 16 | 17 | override public function emit( 18 | x: Float, 19 | y: Float, 20 | vx: Float, 21 | vy: Float, 22 | fireCode: Int, 23 | program: Maybe, 24 | originPositionRef: Maybe 25 | ): Void { 26 | #if debug 27 | if (this.aosoa == null) throw "Emitter not initialized."; 28 | #end 29 | 30 | this.aosoa.use(x, y, vx, vy, program, originPositionRef); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /demo/src/EventHandler.hx: -------------------------------------------------------------------------------- 1 | import sneaker.print.Printer; 2 | 3 | class EventHandler extends firedancer.vm.EventHandler { 4 | public function new() {} 5 | 6 | override public function onGlobalEvent(eventCode: Int): Void { 7 | Printer.println('Invoked global event. (code: $eventCode)'); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /demo/src/Global.hx: -------------------------------------------------------------------------------- 1 | class Global { 2 | public static inline final width: UInt = 600; 3 | public static inline final height: UInt = 800; 4 | 5 | public static var world: Maybe = Maybe.none(); 6 | } 7 | -------------------------------------------------------------------------------- /demo/src/Main.hx: -------------------------------------------------------------------------------- 1 | import broker.scene.SceneStack; 2 | import broker.color.ArgbColor; 3 | 4 | class Main extends broker.App { 5 | static function main() { 6 | new Main(Global.width, Global.height, false); 7 | Dom.initialize(); 8 | } 9 | 10 | var sceneStack: SceneStack; 11 | 12 | override function initialize(): Void { 13 | hxd.Res.initEmbed(); 14 | broker.App.data.engine.backgroundColor = 0xffffffff; 15 | 16 | final initialScene = new scenes.PlayScene(); 17 | initialScene.fadeInFrom(ArgbColor.WHITE, 30, true); 18 | sceneStack = new SceneStack(initialScene, 16).newTag("scene stack"); 19 | } 20 | 21 | override function update() { 22 | sceneStack.update(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /demo/src/World.hx: -------------------------------------------------------------------------------- 1 | import broker.object.Object; 2 | import broker.image.Tile; 3 | import broker.draw.DrawArea; 4 | import broker.draw.TileDraw; 5 | import broker.draw.BatchDraw; 6 | import firedancer.vm.Program; 7 | import firedancer.vm.ProgramPackage; 8 | import firedancer.vm.PositionRef; 9 | import actor.*; 10 | 11 | class World { 12 | public static inline final worldWidth: UInt = Global.width; 13 | public static inline final worldHeight: UInt = Global.height; 14 | static inline final maxAgentCount: UInt = 64; 15 | static inline final maxBulletCount: UInt = 4096; 16 | 17 | /** 18 | The layer that contains all drawable objects in `this` world. 19 | **/ 20 | public final area: DrawArea; 21 | 22 | final armiesContainer: Object; 23 | 24 | var army: Army; 25 | 26 | public function new() { 27 | final area = this.area = new DrawArea(worldWidth, worldHeight); 28 | 29 | final backgroundTile = Tile.fromArgb(0xfffefdff, area.width, area.height); 30 | final background = new TileDraw(backgroundTile); 31 | area.add(background); 32 | 33 | armiesContainer = new Object(); 34 | armiesContainer.setPosition(worldWidth / 2, 0); 35 | area.add(armiesContainer); 36 | // armiesContainer.setFilter(new h2d.filter.Glow(0xFFFFFF, 1.0, 50, 0.5, 0.5, true)); 37 | 38 | final programPackage = BulletPatterns.defaultPattern(); 39 | this.reset(programPackage, programPackage.getProgramByName("main")); 40 | } 41 | 42 | public function reset(programPackage: ProgramPackage, mainProgram: Program): Void { 43 | this.armiesContainer.removeChildren(); 44 | 45 | final targetPositionRef = PositionRef.createImmutable(0, 0.75 * worldHeight); 46 | army = WorldBuilder.createArmy( 47 | this.armiesContainer, 48 | targetPositionRef, 49 | worldWidth, 50 | worldHeight, 51 | programPackage 52 | ); 53 | 54 | army.crashAll(); 55 | 56 | final x = 0.0; 57 | final y = 200.0; 58 | army.newAgent(x, y, 0.0, 0.0, mainProgram); 59 | } 60 | 61 | public function update(): Void { 62 | army.update(); 63 | army.synchronize(); 64 | } 65 | 66 | public function dispose(): Void {} 67 | } 68 | 69 | /** 70 | Functions internally used in `World.new()`. 71 | **/ 72 | @:access(World) 73 | private class WorldBuilder { 74 | public static function createArmy( 75 | parent: Object, 76 | targetPositionRef: PositionRef, 77 | areaWidth: UInt, 78 | areaHeight: UInt, 79 | programPackage: ProgramPackage 80 | ) { 81 | final agentTile = Tile.fromImage(hxd.Res.agent).toCentered(); 82 | final agentBatch = new BatchDraw( 83 | agentTile.getTexture(), 84 | areaWidth, 85 | areaHeight 86 | ); 87 | parent.addChild(agentBatch); 88 | 89 | final bulletTile = Tile.fromImage(hxd.Res.bullet).toCentered(); 90 | final bulletBatch = new BatchDraw( 91 | bulletTile.getTexture(), 92 | areaWidth, 93 | areaHeight 94 | ); 95 | parent.addChild(bulletBatch); 96 | 97 | final eventHandler = new EventHandler(); 98 | final emitter = new Emitter(); 99 | 100 | final bullets = ArmyBuilder.createActors( 101 | World.maxBulletCount, 102 | programPackage, 103 | bulletBatch, 104 | bulletTile, 105 | eventHandler, 106 | emitter 107 | ); 108 | 109 | final agents = ArmyBuilder.createActors( 110 | World.maxAgentCount, 111 | programPackage, 112 | agentBatch, 113 | agentTile, 114 | eventHandler, 115 | emitter 116 | ); 117 | 118 | emitter.initialize(bullets); 119 | 120 | return new Army(agents, bullets, targetPositionRef); 121 | } 122 | } 123 | 124 | /** 125 | Bounds of the habitable zone of actors. 126 | Horizontally centered. 127 | **/ 128 | class HabitableZone { 129 | static extern inline final margin: Float = 64; 130 | public static extern inline final leftX: Float = -World.worldWidth / 2 - margin; 131 | public static extern inline final topY: Float = 0 - margin; 132 | public static extern inline final rightX: Float = World.worldWidth / 2 + margin; 133 | public static extern inline final bottomY: Float = World.worldHeight + margin; 134 | 135 | public static extern inline function containsPoint(x: Float, y: Float): Bool 136 | return y < bottomY && topY <= y && leftX <= x && x < rightX; 137 | } 138 | -------------------------------------------------------------------------------- /demo/src/actor/Actor.hx: -------------------------------------------------------------------------------- 1 | package actor; 2 | 3 | @:banker_verified 4 | class Actor extends broker.entity.BasicBatchEntity { 5 | /** 6 | Object for emitting new bullet. 7 | **/ 8 | @:banker_chunkLevelFinal 9 | var programTable: Vector; 10 | 11 | /** 12 | Object for handling events. 13 | **/ 14 | @:banker_chunkLevelFinal 15 | var eventHandler: EventHandler; 16 | 17 | /** 18 | Object for emitting new bullet. 19 | **/ 20 | @:banker_chunkLevelFinal 21 | var emitter: Emitter; 22 | 23 | /** 24 | Rotation velocity. 25 | **/ 26 | @:banker_chunkLevelFinal 27 | var rotationVelocity: Float = 0.03; 28 | 29 | /** 30 | `firedancer` threads. 31 | **/ 32 | @:banker_factory(() -> new ThreadList(THREAD_COUNT, MEMORY_CAPACITY)) 33 | @:banker_swap 34 | var threads: ThreadList; 35 | 36 | /** 37 | Reference to the origin point. 38 | @see `firedancer.vm.Emitter` 39 | **/ 40 | var originPositionRef: Maybe = Maybe.none(); 41 | 42 | /** 43 | Elapsed frame count of each entity. 44 | **/ 45 | var frameCount: UInt = UInt.zero; 46 | 47 | /** 48 | `true` if the entity should be disused in the next call of `update()`. 49 | May be set in collision detection process. 50 | **/ 51 | var dead: Bool = false; 52 | 53 | @:banker_useEntity 54 | static function use( 55 | sprite: BatchSprite, 56 | x: WVec, 57 | y: WVec, 58 | vx: WVec, 59 | vy: WVec, 60 | threads: ThreadList, 61 | originPositionRef: WVec>, 62 | frameCount: WVec, 63 | dead: WVec, 64 | i: Int, 65 | usedSprites: WVec, 66 | usedCount: Int, 67 | initialX: Float, 68 | initialY: Float, 69 | initialVx: Float, 70 | initialVy: Float, 71 | program: Maybe, 72 | originPosition: Maybe 73 | ): Void { 74 | x[i] = initialX; 75 | y[i] = initialY; 76 | vx[i] = initialVx; 77 | vy[i] = initialVy; 78 | 79 | if (program.isSome()) threads.set(program.unwrap()); 80 | else threads.reset(); 81 | 82 | originPositionRef[i] = originPosition; 83 | 84 | frameCount[i] = UInt.zero; 85 | dead[i] = false; 86 | usedSprites[usedCount] = sprite; 87 | ++usedCount; 88 | 89 | sprite.rotation = 0.0; 90 | } 91 | 92 | @:banker_useEntity 93 | static function emit( 94 | sprite: BatchSprite, 95 | x: WVec, 96 | y: WVec, 97 | vx: WVec, 98 | vy: WVec, 99 | threads: ThreadList, 100 | originPositionRef: WVec>, 101 | frameCount: WVec, 102 | dead: WVec, 103 | i: Int, 104 | usedSprites: WVec, 105 | usedCount: Int, 106 | initialX: Float, 107 | initialY: Float, 108 | speed: Float, 109 | direction: Float, 110 | program: Maybe, 111 | originPosition: Maybe 112 | ): Void { 113 | x[i] = initialX; 114 | y[i] = initialY; 115 | final velocity = Geometry.toVec(speed, direction); 116 | vx[i] = velocity.x; 117 | vy[i] = velocity.y; 118 | 119 | if (program.isSome()) threads.set(program.unwrap()); 120 | else threads.reset(); 121 | 122 | originPositionRef[i] = originPosition; 123 | 124 | frameCount[i] = UInt.zero; 125 | dead[i] = false; 126 | usedSprites[usedCount] = sprite; 127 | ++usedCount; 128 | 129 | sprite.rotation = 0.0; 130 | } 131 | 132 | /** 133 | Disuses all entities currently in use. 134 | **/ 135 | static function crashAll( 136 | sprite: BatchSprite, 137 | disuse: Bool, 138 | disusedSprites: WVec, 139 | disusedCount: Int 140 | ): Void { 141 | disuse = true; 142 | disusedSprites[disusedCount] = sprite; 143 | ++disusedCount; 144 | 145 | PositionRef.invalidate(sprite); 146 | } 147 | 148 | static function update( 149 | programTable: Vector, 150 | sprite: BatchSprite, 151 | x: WVec, 152 | y: WVec, 153 | vx: WVec, 154 | vy: WVec, 155 | threads: ThreadList, 156 | originPositionRef: WVec>, 157 | frameCount: WVec, 158 | i: Int, 159 | disuse: Bool, 160 | disusedSprites: WVec, 161 | disusedCount: Int, 162 | dead: WVec, 163 | rotationVelocity: Float, 164 | eventHandler: EventHandler, 165 | emitter: Emitter, 166 | targetPositionRef: PositionRef 167 | ): Void { 168 | if (dead[i] || !HabitableZone.containsPoint(x[i], y[i])) { 169 | disuse = true; 170 | disusedSprites[disusedCount] = sprite; 171 | ++disusedCount; 172 | PositionRef.invalidate(sprite); 173 | } else { 174 | final endCode = Vm.run( 175 | programTable, 176 | eventHandler, 177 | emitter, 178 | MEMORY_CAPACITY, 179 | threads, 180 | x, 181 | y, 182 | vx, 183 | vy, 184 | originPositionRef, 185 | i, 186 | sprite, 187 | targetPositionRef 188 | ); 189 | 190 | switch endCode { 191 | case -1: dead[i] = true; 192 | default: 193 | } 194 | } 195 | 196 | sprite.rotation += rotationVelocity; 197 | ++frameCount[i]; 198 | } 199 | } 200 | 201 | @:build(banker.aosoa.Chunk.fromStructure(actor.Actor)) 202 | @:banker_verified 203 | class ActorChunk {} 204 | -------------------------------------------------------------------------------- /demo/src/actor/ActorAosoa.hx: -------------------------------------------------------------------------------- 1 | package actor; 2 | 3 | /** 4 | AoSoA of `Actor`. 5 | **/ 6 | @:build(banker.aosoa.Aosoa.fromChunk(actor.Actor.ActorChunk)) 7 | @:banker_verified 8 | class ActorAosoa {} 9 | -------------------------------------------------------------------------------- /demo/src/actor/Army.hx: -------------------------------------------------------------------------------- 1 | package actor; 2 | 3 | class Army implements ripper.Data { 4 | public final agents: ActorAosoa; 5 | public final bullets: ActorAosoa; 6 | public final targetPositionRef: PositionRef; 7 | 8 | public function update() { 9 | this.agents.update(this.targetPositionRef); 10 | this.bullets.update(this.targetPositionRef); 11 | } 12 | 13 | public function synchronize() { 14 | this.agents.synchronize(); 15 | this.bullets.synchronize(); 16 | } 17 | 18 | public function crashAll() { 19 | this.agents.crashAll(); 20 | this.bullets.crashAll(); 21 | } 22 | 23 | public function newAgent( 24 | x: Float, 25 | y: Float, 26 | vx: Float, 27 | vy: Float, 28 | fdProgram: Maybe 29 | ): Void { 30 | this.agents.use(x, y, vx, vy, fdProgram, Maybe.none()); 31 | } 32 | 33 | public function newBullet( 34 | x: Float, 35 | y: Float, 36 | vx: Float, 37 | vy: Float, 38 | fdProgram: Maybe 39 | ): Void { 40 | this.bullets.use(x, y, vx, vy, fdProgram, Maybe.none()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /demo/src/actor/ArmyBuilder.hx: -------------------------------------------------------------------------------- 1 | package actor; 2 | 3 | import firedancer.vm.ProgramPackage; 4 | 5 | /** 6 | Functions internally used in `Army.new()`. 7 | **/ 8 | class ArmyBuilder { 9 | static var defaultChunkCapacity: UInt = 64; 10 | 11 | public static function createActors( 12 | maxEntityCount: UInt, 13 | programPackage: ProgramPackage, 14 | batch: BatchDraw, 15 | tile: Tile, 16 | eventHandler: EventHandler, 17 | emitter: Emitter 18 | ): ActorAosoa { 19 | final chunkCapacity = UInts.min(defaultChunkCapacity, maxEntityCount); 20 | final chunkCount = Math.ceil(maxEntityCount / chunkCapacity); 21 | 22 | final spriteFactory = () -> new BatchSprite(tile); 23 | final programTable = programPackage.programTable; 24 | 25 | final aosoa = new ActorAosoa( 26 | chunkCapacity, 27 | chunkCount, 28 | batch, 29 | spriteFactory, 30 | programTable, 31 | eventHandler, 32 | emitter 33 | ); 34 | 35 | return aosoa; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /demo/src/actor/Constants.hx: -------------------------------------------------------------------------------- 1 | package actor; 2 | 3 | class Constants { 4 | public static extern inline final THREAD_COUNT: UInt = 4; 5 | public static extern inline final MEMORY_CAPACITY: UInt = 128; 6 | } 7 | -------------------------------------------------------------------------------- /demo/src/actor/import.hx: -------------------------------------------------------------------------------- 1 | package actor; 2 | 3 | import banker.vector.Vector; 4 | import banker.vector.WritableVector as WVec; 5 | import banker.types.Reference; 6 | import banker.aosoa.ChunkEntityId; 7 | import broker.geometry.Aabb; 8 | import broker.image.Tile; 9 | import broker.draw.BatchDraw; 10 | import broker.draw.BatchSprite; 11 | import firedancer.vm.PositionRef; 12 | import firedancer.vm.EventHandler; 13 | import firedancer.vm.Geometry; 14 | import firedancer.vm.Program; 15 | import firedancer.vm.Vm; 16 | import firedancer.vm.ThreadList; 17 | import actor.Constants.*; 18 | import World.HabitableZone; 19 | -------------------------------------------------------------------------------- /demo/src/import.hx: -------------------------------------------------------------------------------- 1 | import sinker.*; 2 | 3 | using sinker.extensions.Index; 4 | -------------------------------------------------------------------------------- /demo/src/scenes/PlayScene.hx: -------------------------------------------------------------------------------- 1 | package scenes; 2 | 3 | import broker.App; 4 | import broker.scene.Scene; 5 | 6 | class PlayScene extends Scene { 7 | var world: World; 8 | 9 | override function initialize(): Void { 10 | super.initialize(); 11 | 12 | this.world = new World(); 13 | final worldArea = this.world.area; 14 | worldArea.setCenterPosition(App.width / 2, App.height / 2); 15 | this.layers.main.add(worldArea); 16 | 17 | Global.world = Maybe.from(this.world); 18 | } 19 | 20 | override function update(): Void { 21 | super.update(); 22 | this.world.update(); 23 | 24 | if (false) { 25 | // TODO: user input 26 | this.newPlayScene(); 27 | return; 28 | } 29 | } 30 | 31 | override function destroy(): Void { 32 | this.world.dispose(); 33 | Global.world = Maybe.none(); 34 | } 35 | 36 | function newPlayScene(): Void { 37 | if (this.isTransitioning) return; 38 | final nextScene = new PlayScene(); 39 | this.switchTo(nextScene, UInt.zero, true, true); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /haxelib.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "firedancer", 3 | "url": "https://github.com/fal-works/firedancer/", 4 | "license": "MIT", 5 | "tags": ["game"], 6 | "description": "Haxe based language for defining 2D shmup bullet-hell patterns. ", 7 | "version": "0.1.3", 8 | "classPath": "src/", 9 | "releasenote": "Improved duplicate program reduction. Separated website to another repository.", 10 | "contributors": [ 11 | "fal-works" 12 | ], 13 | "dependencies": { 14 | "sinker": "", 15 | "reckoner": "", 16 | "ripper": "", 17 | "sneaker": "", 18 | "banker": "", 19 | "firedancer-vm": "" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /hl-jit-test.hxml: -------------------------------------------------------------------------------- 1 | # Build and Test with HL/JIT 2 | 3 | hl-jit.hxml 4 | -cmd hl out/main.hl 5 | -------------------------------------------------------------------------------- /hl-jit.hxml: -------------------------------------------------------------------------------- 1 | # Build with HL/JIT 2 | 3 | common.hxml 4 | 5 | -lib hldx 6 | # -lib hlsdl 7 | 8 | -hl out/main.hl 9 | -------------------------------------------------------------------------------- /hxformat.json: -------------------------------------------------------------------------------- 1 | { 2 | "indentation": { 3 | "character": "tab", 4 | "tabWidth": 2, 5 | "conditionalPolicy": "alignedNestedIncrease", 6 | "indentCaseLabels": false 7 | }, 8 | "sameLine": { 9 | "caseBody": "keep", 10 | "elseBody": "keep", 11 | "elseIf": "keep", 12 | "forBody": "keep", 13 | "ifBody": "keep", 14 | "ifElse": "keep", 15 | "whileBody": "keep" 16 | }, 17 | "whitespace": { 18 | "typeHintColonPolicy": "onlyAfter", 19 | "bracesConfig": { 20 | "anonTypeBraces": { 21 | "openingPolicy": "around", 22 | "closingPolicy": "around", 23 | "removeInnerWhenEmpty": true 24 | }, 25 | "blockBraces": { 26 | "openingPolicy": "around", 27 | "closingPolicy": "around", 28 | "removeInnerWhenEmpty": true 29 | }, 30 | "objectLiteralBraces": { 31 | "openingPolicy": "around", 32 | "closingPolicy": "around", 33 | "removeInnerWhenEmpty": true 34 | }, 35 | "typedefBraces": { 36 | "openingPolicy": "around", 37 | "closingPolicy": "around", 38 | "removeInnerWhenEmpty": true 39 | }, 40 | "unknownBraces": { 41 | "openingPolicy": "around", 42 | "closingPolicy": "around", 43 | "removeInnerWhenEmpty": true 44 | } 45 | } 46 | }, 47 | "wrapping": { 48 | "anonFunctionSignature": { 49 | "defaultWrap": "noWrap", 50 | "rules": [ 51 | { 52 | "conditions": [ 53 | { 54 | "cond": "itemCount >= n", 55 | "value": 7 56 | } 57 | ], 58 | "type": "onePerLine", 59 | "additionalIndent": 1 60 | }, 61 | { 62 | "conditions": [ 63 | { 64 | "cond": "totalItemLength >= n", 65 | "value": 60 66 | } 67 | ], 68 | "type": "onePerLine", 69 | "additionalIndent": 1 70 | }, 71 | { 72 | "conditions": [ 73 | { 74 | "cond": "lineLength >= n", 75 | "value": 80 76 | } 77 | ], 78 | "type": "onePerLine", 79 | "additionalIndent": 1 80 | } 81 | ] 82 | }, 83 | "arrayWrap": { 84 | "defaultWrap": "onePerLine", 85 | "rules": [ 86 | { 87 | "conditions": [ 88 | { 89 | "cond": "totalItemLength >= n", 90 | "value": 60 91 | } 92 | ], 93 | "type": "onePerLine" 94 | }, 95 | { 96 | "conditions": [ 97 | { 98 | "cond": "itemCount <= n", 99 | "value": 3 100 | } 101 | ], 102 | "type": "keep" 103 | }, 104 | { 105 | "conditions": [ 106 | { 107 | "cond": "itemCount >= n", 108 | "value": 10 109 | } 110 | ], 111 | "type": "noWrap" 112 | } 113 | ] 114 | }, 115 | "callParameter": { 116 | "defaultWrap": "noWrap", 117 | "rules": [ 118 | { 119 | "conditions": [ 120 | { 121 | "cond": "totalItemLength >= n", 122 | "value": 60 123 | } 124 | ], 125 | "type": "onePerLine" 126 | }, 127 | { 128 | "conditions": [ 129 | { 130 | "cond": "itemCount <= n", 131 | "value": 1 132 | } 133 | ], 134 | "type": "noWrap" 135 | }, 136 | { 137 | "conditions": [ 138 | { 139 | "cond": "itemCount <= n", 140 | "value": 3 141 | } 142 | ], 143 | "type": "keep" 144 | }, 145 | { 146 | "conditions": [ 147 | { 148 | "cond": "lineLength >= n", 149 | "value": 81 150 | } 151 | ], 152 | "type": "onePerLine" 153 | }, 154 | { 155 | "conditions": [ 156 | { 157 | "cond": "itemCount >= n", 158 | "value": 7 159 | } 160 | ], 161 | "type": "fillLine" 162 | }, 163 | { 164 | "conditions": [ 165 | { 166 | "cond": "anyItemLength >= n", 167 | "value": 90 168 | } 169 | ], 170 | "type": "fillLine" 171 | } 172 | ] 173 | }, 174 | "functionSignature": { 175 | "defaultWrap": "fillLine", 176 | "rules": [ 177 | { 178 | "conditions": [ 179 | { 180 | "cond": "totalItemLength <= n", 181 | "value": 12 182 | } 183 | ], 184 | "type": "fillLine" 185 | }, 186 | { 187 | "conditions": [ 188 | { 189 | "cond": "itemCount >= n", 190 | "value": 3 191 | } 192 | ], 193 | "type": "onePerLine" 194 | }, 195 | { 196 | "conditions": [ 197 | { 198 | "cond": "totalItemLength >= n", 199 | "value": 60 200 | } 201 | ], 202 | "type": "onePerLine" 203 | }, 204 | { 205 | "conditions": [ 206 | { 207 | "cond": "lineLength >= n", 208 | "value": 90 209 | } 210 | ], 211 | "type": "onePerLine" 212 | } 213 | ] 214 | }, 215 | "implementsExtends": { 216 | "defaultWrap": "noWrap", 217 | "rules": [ 218 | { 219 | "conditions": [ 220 | { 221 | "cond": "lineLength >= n", 222 | "value": 90 223 | } 224 | ], 225 | "type": "onePerLine", 226 | "additionalIndent": 1 227 | }, 228 | { 229 | "conditions": [ 230 | { 231 | "cond": "itemCount >= n", 232 | "value": 4 233 | } 234 | ], 235 | "type": "onePerLine", 236 | "additionalIndent": 1 237 | } 238 | ] 239 | }, 240 | "metadataCallParameter": { 241 | "defaultWrap": "noWrap", 242 | "rules": [ 243 | { 244 | "conditions": [ 245 | { 246 | "cond": "itemCount <= n", 247 | "value": 2 248 | } 249 | ], 250 | "type": "noWrap" 251 | }, 252 | { 253 | "conditions": [ 254 | { 255 | "cond": "totalItemLength >= n", 256 | "value": 70 257 | } 258 | ], 259 | "type": "onePerLine" 260 | }, 261 | { 262 | "conditions": [ 263 | { 264 | "cond": "lineLength >= n", 265 | "value": 80 266 | } 267 | ], 268 | "type": "onePerLine" 269 | } 270 | ] 271 | }, 272 | "typeParameter": { 273 | "defaultWrap": "noWrap", 274 | "rules": [ 275 | { 276 | "conditions": [ 277 | { 278 | "cond": "itemCount <= n", 279 | "value": 2 280 | } 281 | ], 282 | "type": "keep" 283 | }, 284 | { 285 | "conditions": [ 286 | { 287 | "cond": "anyItemLength >= n", 288 | "value": 50 289 | } 290 | ], 291 | "type": "fillLine" 292 | }, 293 | { 294 | "conditions": [ 295 | { 296 | "cond": "totalItemLength >= n", 297 | "value": 70 298 | } 299 | ], 300 | "type": "fillLine" 301 | } 302 | ] 303 | }, 304 | "maxLineLength": 90 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /js.hxml: -------------------------------------------------------------------------------- 1 | # Build with JavaScript 2 | 3 | common.hxml 4 | -D sneaker_assertion_disable 5 | -D js-es=6 6 | -js out/main.js 7 | --cmd terser out/main.js --compress --mangle --ecma 2015 --output web/main.min.js 8 | -------------------------------------------------------------------------------- /res/agent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fal-works/firedancer/4a15e80fe55893bf9b2097ee112a71ea6306815a/res/agent.png -------------------------------------------------------------------------------- /res/bullet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fal-works/firedancer/4a15e80fe55893bf9b2097ee112a71ea6306815a/res/bullet.png -------------------------------------------------------------------------------- /src/firedancer/assembly/Assembler.hx: -------------------------------------------------------------------------------- 1 | package firedancer.assembly; 2 | 3 | import firedancer.vm.Program; 4 | 5 | class Assembler { 6 | public static function assemble(code: AssemblyCode): Program { 7 | final labelPositionMap = new Map(); 8 | final instructions: Array = []; 9 | var lengthInBytes = UInt.zero; 10 | 11 | // consume labels 12 | for (i in 0...code.length) { 13 | final cur = code[i]; 14 | switch cur { 15 | case Label(labelId): 16 | labelPositionMap.set(labelId, lengthInBytes); 17 | default: 18 | instructions.push(cur); 19 | lengthInBytes += cur.bytecodeLength(); 20 | } 21 | } 22 | 23 | final variableTable = new VariableTable(); 24 | final words: WordArray = []; 25 | 26 | for (i in 0...instructions.length) { 27 | final instruction = instructions[i]; 28 | final curWords = instruction.toWordArray(labelPositionMap, variableTable); 29 | 30 | // if (curWords.length > 0) 31 | // Sys.println('[${words.getLengthInBytes()}] ${curWords.toString()}'); 32 | 33 | words.pushFromArray(curWords); 34 | } 35 | 36 | return words.toProgram(); 37 | } 38 | } 39 | 40 | class VariableTable { 41 | static function notFound(key: String): String 42 | return 'Variable not found: $key'; 43 | 44 | /** 45 | List of variable records. 46 | Should be sorted by `address` in ascending order. 47 | **/ 48 | final table: Array<{key: String, address: UInt, type: ValueType }> = []; 49 | 50 | final addressStackMap = new Map>(); 51 | 52 | public function new() {} 53 | 54 | public function let(key: String, type: ValueType): Void { 55 | var address = UInt.zero; 56 | var inserted = false; 57 | 58 | for (i in 0...table.length) { 59 | final entry = table[i]; 60 | if (address + type.size < entry.address) { 61 | table.insert(i, { key: key, address: address, type: type }); 62 | inserted = true; 63 | break; 64 | } else { 65 | address = entry.address + entry.type.size; 66 | } 67 | } 68 | 69 | if (!inserted) table.push({ key: key, address: address, type: type }); 70 | 71 | final addressStack = this.addressStackMap.get(key); 72 | if (addressStack != null) addressStack.push(address); 73 | else this.addressStackMap.set(key, [address]); 74 | } 75 | 76 | public function getAddress(key: String): UInt { 77 | final addressStack = this.addressStackMap.get(key); 78 | if (addressStack == null) throw notFound(key); 79 | 80 | final address = addressStack.getLastSafe(); 81 | if (address.isNone()) throw notFound(key); 82 | 83 | return address.unwrap(); 84 | } 85 | 86 | public function free(key: String): Void { 87 | final addressStack = this.addressStackMap.get(key); 88 | if (addressStack == null) throw notFound(key); 89 | 90 | final address = addressStack.pop().unwrap(); 91 | 92 | final table = this.table; 93 | for (i in 0...table.length) { 94 | final entry = table[i]; 95 | if (entry.key == key && entry.address == address) { 96 | table.removeAt(i); 97 | break; 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/firedancer/assembly/AssemblyCode.hx: -------------------------------------------------------------------------------- 1 | package firedancer.assembly; 2 | 3 | private typedef Data = Array; 4 | 5 | /** 6 | Represents bullet pattern code written in a virtual assembly language. 7 | **/ 8 | @:notNull @:forward 9 | abstract AssemblyCode(Data) from Data to Data { 10 | @:from static extern inline function fromInstruction( 11 | instruction: Instruction 12 | ): AssemblyCode 13 | return [instruction]; 14 | 15 | /** 16 | @return The bytecode length in bytes after assembled. 17 | **/ 18 | public function bytecodeLength(): UInt { 19 | var len = UInt.zero; 20 | for (i in 0...this.length) len += this[i].bytecodeLength(); 21 | return len; 22 | } 23 | 24 | /** 25 | Deeply compares `this` and `other`. 26 | **/ 27 | public function equals(other: AssemblyCode): Bool { 28 | if (this == other) return true; 29 | if (this.length != other.length) return false; 30 | 31 | for (i in 0...this.length) 32 | if (!this[i].equals(other[i])) return false; 33 | 34 | return true; 35 | } 36 | 37 | /** 38 | @return `this` in `String` representation. 39 | **/ 40 | public function toString(): String 41 | return this.map(instruction -> instruction.toString()).join("\n") + "\n"; 42 | } 43 | -------------------------------------------------------------------------------- /src/firedancer/assembly/AssemblyCodePackage.hx: -------------------------------------------------------------------------------- 1 | package firedancer.assembly; 2 | 3 | import sneaker.string_buffer.StringBuffer; 4 | import sneaker.print.Printer; 5 | import banker.vector.Vector; 6 | import banker.vector.WritableVector; 7 | import firedancer.vm.ProgramPackage; 8 | 9 | @:structInit 10 | class AssemblyCodePackage { 11 | /** 12 | List of `AssemblyCode` that should be able to retrieved by an `UInt` ID. 13 | **/ 14 | final codeList: Array; 15 | 16 | /** 17 | Mapping from names to ID numbers of `AssemblyCode` instances. 18 | **/ 19 | final nameIndexMap: Map; 20 | 21 | /** 22 | @return New `AssemblyCodePackage` with all `AssemblyCode` optimized. 23 | **/ 24 | public function optimize(): AssemblyCodePackage { 25 | return { 26 | codeList: this.codeList.map(Optimizer.optimize), 27 | nameIndexMap: this.nameIndexMap 28 | }; 29 | } 30 | 31 | /** 32 | Creates a `ProgramPackage` that contains all `Program` instances assembled from `this` package. 33 | **/ 34 | public function assemble(): ProgramPackage { 35 | final assembled = this.codeList.map(Assembler.assemble); 36 | final bytecodeList = Vector.fromArrayCopy(assembled); 37 | 38 | return new ProgramPackage(bytecodeList, this.nameIndexMap); 39 | } 40 | 41 | /** 42 | Converts all `AssemblyCode` in `this` package into a `String`. 43 | **/ 44 | public function toString(): String { 45 | final codeList = this.codeList; 46 | final names = new WritableVector>(codeList.length); 47 | for (name => id in this.nameIndexMap) names[id] = name; 48 | 49 | final buf = new StringBuffer(); 50 | 51 | final lastId = codeList.length - 1; 52 | for (id in 0...codeList.length) { 53 | final name = Nulls.coalesce(names[id], "(anonymous)"); 54 | buf.addLf('[ASSEMBLY CODE] ID: $id, name: $name'); 55 | buf.add('${codeList[id].toString()}'); 56 | if (id < lastId) buf.lf(); 57 | } 58 | 59 | return buf.toString(); 60 | } 61 | 62 | /** 63 | Prints all `AssemblyCode` in `this` package. 64 | **/ 65 | public function printAll(): Void 66 | Printer.print(this.toString()); 67 | } 68 | -------------------------------------------------------------------------------- /src/firedancer/assembly/Builder.hx: -------------------------------------------------------------------------------- 1 | package firedancer.assembly; 2 | 3 | import firedancer.vm.FireArgument; 4 | import firedancer.script.CompileContext; 5 | import firedancer.script.expression.IntExpression; 6 | 7 | /** 8 | Functions for creating `Instruction`/`AssemblyCode` instances. 9 | **/ 10 | class Builder { 11 | /** 12 | Creates a code instance that repeats `body` in runtime. 13 | **/ 14 | public static function constructLoop( 15 | context: CompileContext, 16 | pushLoopCount: AssemblyCode, 17 | body: AssemblyCode 18 | ) { 19 | final nextLabelIdStack = context.nextLabelIdStack; 20 | var nextLabelId = nextLabelIdStack.pop().unwrap(); 21 | final startLabelId = nextLabelId++; 22 | final endLabelId = nextLabelId++; 23 | nextLabelIdStack.push(nextLabelId); 24 | 25 | final prepareLoop: AssemblyCode = []; 26 | prepareLoop.pushFromArray(pushLoopCount); 27 | prepareLoop.push(Label(startLabelId)); 28 | prepareLoop.push(CountDownGotoLabel(endLabelId)); 29 | 30 | final closeLoop: AssemblyCode = [GotoLabel(startLabelId), Label(endLabelId)]; 31 | 32 | return [ 33 | prepareLoop, 34 | body, 35 | closeLoop 36 | ].flatten(); 37 | } 38 | 39 | /** 40 | Creates a code instance that repeats `body` in runtime. 41 | 42 | Use `constructLoop()` to avoid evaluating `count` and provide the preparation code instead. 43 | **/ 44 | public static function loop( 45 | context: CompileContext, 46 | body: AssemblyCode, 47 | count: IntExpression 48 | ): AssemblyCode { 49 | final pushLoopCount: AssemblyCode = count.load(context); 50 | pushLoopCount.push(Push(Int(Reg))); 51 | 52 | return constructLoop(context, pushLoopCount, body); 53 | } 54 | 55 | /** 56 | Creates a code instance with `bodyFactory` repeated in compile-time. 57 | **/ 58 | public static function loopUnrolled( 59 | iterator: IntIterator, 60 | bodyFactory: (index: Int) -> AssemblyCode 61 | ): AssemblyCode { 62 | return [for (i in iterator) bodyFactory(i)].flatten(); 63 | } 64 | 65 | /** 66 | Creates an instruction with firing opcode. 67 | **/ 68 | public static inline function fire( 69 | fireArgument: Maybe, 70 | fireCode: Int = 0 71 | ): Instruction { 72 | return if (fireArgument.isNone()) { 73 | Fire(switch fireCode { 74 | case 0: Simple; 75 | default: SimpleWithCode(fireCode); 76 | }); 77 | } else { 78 | final arg = fireArgument.unwrap(); 79 | Fire(switch fireCode { 80 | case 0: Complex(arg); 81 | default: ComplexWithCode(arg, fireCode); 82 | }); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/firedancer/assembly/DataRegisterSpecifier.hx: -------------------------------------------------------------------------------- 1 | package firedancer.assembly; 2 | 3 | enum abstract DataRegisterSpecifier(Int) { 4 | /** 5 | @return The main register that corresponds to `type`. 6 | **/ 7 | public static function get(type: ValueType): DataRegisterSpecifier { 8 | return switch type { 9 | case Int: Ri; 10 | case Float: Rf; 11 | case Vec: Rvec; 12 | } 13 | } 14 | 15 | /** 16 | The main integer data register. 17 | **/ 18 | final Ri; 19 | 20 | /** 21 | The integer data register for buffering purpose. 22 | **/ 23 | final Rib; 24 | 25 | /** 26 | The main floating-point data register. 27 | **/ 28 | final Rf; 29 | 30 | /** 31 | The floating-point data register for buffering purpose. 32 | **/ 33 | final Rfb; 34 | 35 | /** 36 | The 2D vector data register. 37 | **/ 38 | final Rvec; 39 | 40 | /** 41 | @return The `ValueType` that corresponds to `this`. 42 | **/ 43 | public function getType(): ValueType { 44 | final reg: DataRegisterSpecifier = cast this; 45 | return switch reg { 46 | case Ri | Rib: Int; 47 | case Rf | Rfb: Float; 48 | case Rvec: Vec; 49 | }; 50 | } 51 | 52 | /** 53 | @return The corresponding buffer register for `this`. 54 | **/ 55 | public function getBuffer(): DataRegisterSpecifier { 56 | final reg: DataRegisterSpecifier = cast this; 57 | return switch reg { 58 | case Ri: Rib; 59 | case Rf: Rfb; 60 | default: throw 'Buffer is not available for ${reg.toString()}'; 61 | }; 62 | } 63 | 64 | @:to public function toString(): String { 65 | return switch (cast this : DataRegisterSpecifier) { 66 | case Ri: "ri"; 67 | case Rib: "rib"; 68 | case Rf: "rf"; 69 | case Rfb: "rfb"; 70 | case Rvec: "rvec"; 71 | }; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/firedancer/assembly/Instruction.hx: -------------------------------------------------------------------------------- 1 | package firedancer.assembly; 2 | 3 | import firedancer.assembly.Operand; 4 | import firedancer.assembly.types.*; 5 | 6 | @:using(firedancer.assembly.InstructionExtension) 7 | @:using(firedancer.assembly.InstructionOptimizer) 8 | @:using(firedancer.assembly.InstructionAssembler) 9 | enum Instruction { 10 | // ---- control flow -------------------------------------------------------- 11 | Break; 12 | CountDownBreak; 13 | Label(labelId: UInt); 14 | GotoLabel(labelId: UInt); 15 | CountDownGotoLabel(labelId: UInt); 16 | UseThread(programId: UInt, output: Operand); 17 | AwaitThread; 18 | End(endCode: Int); 19 | // ---- move values --------------------------------------------------------- 20 | Load(input: Operand); 21 | Save(input: Operand); 22 | // ---- variables ----------------------------------------------------------- 23 | Let(varKey: String, type: ValueType); 24 | Store(input: Operand, varKey: String); 25 | Free(varKey: String, type: ValueType); 26 | // ---- read/write stack ---------------------------------------------------- 27 | Push(input: Operand); 28 | Pop(type: ValueType); 29 | Peek(type: ValueType, bytesToSkip: Int); 30 | Drop(type: ValueType); 31 | // ---- fire ---------------------------------------------------------------- 32 | Fire(fireType: FireType); 33 | // ---- other general ------------------------------------------------------- 34 | Event(eventType: EventType); 35 | Debug(debugCode: Int); 36 | // ---- calc values --------------------------------------------------------- 37 | Add(input: OperandPair); 38 | Sub(input: OperandPair); 39 | Minus(input: Operand); 40 | Mult(inputA: Operand, inputB: Operand); 41 | Div(inputA: Operand, inputB: Operand); 42 | Mod(inputA: Operand, inputB: Operand); 43 | Cast(castType: CastOperationType); 44 | RandomRatio; 45 | Random(max: Operand); 46 | RandomSigned(maxMagnitude: Operand); 47 | Sin; 48 | Cos; 49 | Increment(address: String); 50 | Decrement(address: String); 51 | // ---- read actor data ----------------------------------------------------- 52 | Get(property: ActorProperty); 53 | GetTarget(prop: TargetProperty); 54 | GetDiff(input: Operand, property: ActorProperty); 55 | // ---- write actor data ---------------------------------------------------- 56 | Set(input: Operand, property: ActorProperty); 57 | Increase(input: Operand, property: ActorProperty); 58 | // ---- no effects ---------------------------------------------------------- 59 | Comment(text: String); 60 | None; 61 | } 62 | -------------------------------------------------------------------------------- /src/firedancer/assembly/OperandKind.hx: -------------------------------------------------------------------------------- 1 | package firedancer.assembly; 2 | 3 | enum abstract OperandKind(Int) { 4 | final Null; 5 | final Imm; 6 | final Reg; 7 | final RegBuf; 8 | final Stack; 9 | final Var; 10 | } 11 | -------------------------------------------------------------------------------- /src/firedancer/assembly/OperandTools.hx: -------------------------------------------------------------------------------- 1 | package firedancer.assembly; 2 | 3 | class OperandTools { 4 | public static function ftoa(v: Float): String 5 | return if (Floats.toInt(v) == v) '$v.0' else Std.string(v); 6 | 7 | public static function varToString(key: String, type: ValueType): String { 8 | if (key.getIndexOf("\"").isSome()) 9 | throw 'Invalid variable key. Contains double quote: $key'; 10 | 11 | final typeChar = switch type { 12 | case Int: "i"; 13 | case Float: "f"; 14 | case Vec: "v"; 15 | }; 16 | return '${typeChar}var[\"$key\"]'; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/firedancer/assembly/ValueType.hx: -------------------------------------------------------------------------------- 1 | package firedancer.assembly; 2 | 3 | import firedancer.vm.Constants.*; 4 | 5 | enum abstract ValueType(Int) { 6 | final Int; 7 | final Float; 8 | final Vec; 9 | 10 | /** 11 | The size in bytes of a single value of `this` type. 12 | **/ 13 | public var size(get, never): UInt; 14 | 15 | public function toString(): String { 16 | return switch (cast this : ValueType) { 17 | case Int: "int"; 18 | case Float: "float"; 19 | case Vec: "vec"; 20 | } 21 | } 22 | 23 | extern inline function get_size(): UInt { 24 | return switch (cast this : ValueType) { 25 | case Int: IntSize; 26 | case Float: FloatSize; 27 | case Vec: VecSize; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/firedancer/assembly/Word.hx: -------------------------------------------------------------------------------- 1 | package firedancer.assembly; 2 | 3 | import haxe.Int32; 4 | import firedancer.vm.Opcode; 5 | import firedancer.vm.OpcodeExtension; 6 | 7 | /** 8 | Data unit in firedancer `Program`. 9 | **/ 10 | abstract Word(WordEnum) from WordEnum { 11 | @:from public static inline function fromOpcode(opcode: Opcode): Word 12 | return OpcodeWord(opcode); 13 | 14 | @:from public static inline function fromInt(value: Int): Word 15 | return IntWord(value); 16 | 17 | @:from public static inline function fromInt32(value: Int32): Word 18 | return IntWord(value); 19 | 20 | @:from public static inline function fromFloat(value: Float): Word 21 | return FloatWord(value); 22 | 23 | @:to public inline function toEnum(): WordEnum 24 | return this; 25 | 26 | @:to public function toString(): String { 27 | inline function ftoa(v: Float): String 28 | return if (Floats.toInt(v) == v) '$v.0' else Std.string(v); 29 | 30 | return switch this { 31 | case OpcodeWord(code): OpcodeExtension.toString(code); 32 | case IntWord(value): Std.string(value); 33 | case FloatWord(value): ftoa(value); 34 | } 35 | } 36 | } 37 | 38 | enum WordEnum { 39 | /** 40 | Operation code i.e. a value that specifies an operation to be performed. 41 | **/ 42 | OpcodeWord(code: Opcode); 43 | 44 | /** 45 | Integer operand. 46 | **/ 47 | IntWord(value: Int32); 48 | 49 | /** 50 | Float operand. 51 | **/ 52 | FloatWord(value: Float); 53 | } 54 | -------------------------------------------------------------------------------- /src/firedancer/assembly/WordArray.hx: -------------------------------------------------------------------------------- 1 | package firedancer.assembly; 2 | 3 | import haxe.Int32; 4 | import banker.binary.Bytes; 5 | import firedancer.vm.Program; 6 | import firedancer.vm.Opcode; 7 | import firedancer.vm.Constants.*; 8 | import firedancer.assembly.Word.WordEnum; 9 | 10 | private typedef Data = Array; 11 | 12 | /** 13 | Array of `Word` values. 14 | **/ 15 | @:notNull @:forward 16 | abstract WordArray(Data) from Data to Data { 17 | @:from static inline function fromWords(words: std.Array): WordArray 18 | return cast words; 19 | 20 | @:from static inline function fromWord(word: Word): WordArray 21 | return [word]; 22 | 23 | @:from static inline function fromOpcode(opcode: Opcode): WordArray 24 | return [OpcodeWord(opcode)]; 25 | 26 | /** 27 | @return The total length in bytes. 28 | **/ 29 | public inline function getLengthInBytes(): UInt { 30 | var length = UInt.zero; 31 | 32 | for (i in 0...this.length) { 33 | final unit = this[i]; 34 | length += switch unit.toEnum() { 35 | case OpcodeWord(_): Opcode.size; 36 | case IntWord(_): IntSize; 37 | case FloatWord(_): FloatSize; 38 | } 39 | } 40 | 41 | return length; 42 | } 43 | 44 | public function toString(): String 45 | return this.map(word -> word.toString()).join(" "); 46 | 47 | /** 48 | Compiles `this` words to firedancer `Program`. 49 | **/ 50 | public inline function toProgram(): Program { 51 | final bytes = Bytes.alloc(getLengthInBytes()); 52 | final data = bytes.data; 53 | var pos = UInt.zero; 54 | 55 | inline function addOpcode(code: Opcode): Void { 56 | data.setUI8(pos, code.int()); 57 | pos += Opcode.size; 58 | } 59 | 60 | inline function addInt(v: Int32): Void { 61 | data.setI32(pos, v); 62 | pos += IntSize; 63 | } 64 | 65 | inline function addFloat(v: Float): Void { 66 | data.setF64(pos, v); 67 | pos += FloatSize; 68 | } 69 | 70 | for (i in 0...this.length) { 71 | final unit = this[i]; 72 | switch unit.toEnum() { 73 | case OpcodeWord(byte): addOpcode(byte); 74 | case IntWord(v): addInt(v); 75 | case FloatWord(v): addFloat(v); 76 | } 77 | } 78 | 79 | return bytes; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/firedancer/assembly/types/ActorProperty.hx: -------------------------------------------------------------------------------- 1 | package firedancer.assembly.types; 2 | 3 | @:notNull @:forward 4 | @:using(firedancer.assembly.types.ActorPropertyExtension) 5 | abstract ActorProperty(Data) from Data { 6 | public static function create( 7 | type: ActorPropertyType, 8 | component: ActorPropertyComponent 9 | ): ActorProperty { 10 | final data: Data = { type: type, component: component }; 11 | return data; 12 | } 13 | 14 | @:op(A == B) inline function equals(other: ActorProperty): Bool 15 | return this.type == other.type && this.component == other.component; 16 | } 17 | 18 | @:structInit 19 | private class Data { 20 | public final type: ActorPropertyType; 21 | public final component: ActorPropertyComponent; 22 | 23 | public inline function getValueType(): ValueType 24 | return component.getValueType(); 25 | 26 | public function toString(): String { 27 | return switch type { 28 | case Position: 29 | switch component { 30 | case Vector: "position"; 31 | case Length: "distance"; 32 | case Angle: "bearing"; 33 | } 34 | case Velocity: 35 | switch component { 36 | case Vector: "velocity"; 37 | case Length: "speed"; 38 | case Angle: "direction"; 39 | } 40 | case ShotPosition: 41 | switch component { 42 | case Vector: "shot_position"; 43 | case Length: "shot_distance"; 44 | case Angle: "shot_bearing"; 45 | } 46 | case ShotVelocity: 47 | switch component { 48 | case Vector: "shot_velocity"; 49 | case Length: "shot_speed"; 50 | case Angle: "shot_direction"; 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/firedancer/assembly/types/ActorPropertyComponent.hx: -------------------------------------------------------------------------------- 1 | package firedancer.assembly.types; 2 | 3 | /** 4 | Type of actor's property component to be operated. 5 | **/ 6 | enum abstract ActorPropertyComponent(Int) { 7 | final Vector; 8 | final Length; 9 | final Angle; 10 | 11 | /** 12 | @return The corresponding `ValueType`. 13 | **/ 14 | public function getValueType(): ValueType { 15 | return switch (cast this : ActorPropertyComponent) { 16 | case Vector: Vec; 17 | case Length | Angle: Float; 18 | } 19 | } 20 | 21 | public function toString(): String { 22 | return switch (cast this : ActorPropertyComponent) { 23 | case Vector: "vector"; 24 | case Length: "length"; 25 | case Angle: "angle"; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/firedancer/assembly/types/ActorPropertyExtension.hx: -------------------------------------------------------------------------------- 1 | package firedancer.assembly.types; 2 | 3 | import firedancer.vm.Opcode; 4 | 5 | class ActorPropertyExtension { 6 | public static function getReadOpcode(_this: ActorProperty): Opcode { 7 | return Opcode.read(switch _this.type { 8 | case Position: 9 | switch _this.component { 10 | case Vector: LoadPositionR; 11 | case Length: LoadDistanceR; 12 | case Angle: LoadBearingR; 13 | } 14 | case Velocity: 15 | switch _this.component { 16 | case Vector: LoadVelocityR; 17 | case Length: LoadSpeedR; 18 | case Angle: LoadDirectionR; 19 | } 20 | case ShotPosition: 21 | switch _this.component { 22 | case Vector: LoadShotPositionR; 23 | case Length: LoadShotDistanceR; 24 | case Angle: LoadShotBearingR; 25 | } 26 | case ShotVelocity: 27 | switch _this.component { 28 | case Vector: LoadShotVelocityR; 29 | case Length: LoadShotSpeedR; 30 | case Angle: LoadShotDirectionR; 31 | } 32 | }); 33 | } 34 | 35 | public static function getWriteOpcode( 36 | _this: ActorProperty, 37 | operationType: ActorPropertyOperationType, 38 | inputKind: OperandKind 39 | ): Opcode { 40 | return Opcode.write(switch _this.type { 41 | case Position: 42 | switch _this.component { 43 | case Vector: 44 | switch operationType { 45 | case Set: 46 | switch inputKind { 47 | case Imm: SetPositionC; 48 | case Reg: SetPositionR; 49 | default: throw unsupported(); 50 | } 51 | case Add: 52 | switch inputKind { 53 | case Imm: AddPositionC; 54 | case Reg: AddPositionR; 55 | case Stack: AddPositionS; 56 | default: throw unsupported(); 57 | } 58 | } 59 | case Length: 60 | switch operationType { 61 | case Set: 62 | switch inputKind { 63 | case Imm: SetDistanceC; 64 | case Reg: SetDistanceR; 65 | default: throw unsupported(); 66 | } 67 | case Add: 68 | switch inputKind { 69 | case Imm: AddDistanceC; 70 | case Reg: AddDistanceR; 71 | case Stack: AddDistanceS; 72 | default: throw unsupported(); 73 | } 74 | } 75 | case Angle: 76 | switch operationType { 77 | case Set: 78 | switch inputKind { 79 | case Imm: SetBearingC; 80 | case Reg: SetBearingR; 81 | default: throw unsupported(); 82 | } 83 | case Add: 84 | switch inputKind { 85 | case Imm: AddBearingC; 86 | case Reg: AddBearingR; 87 | case Stack: AddBearingS; 88 | default: throw unsupported(); 89 | } 90 | } 91 | } 92 | case Velocity: 93 | switch _this.component { 94 | case Vector: 95 | switch operationType { 96 | case Set: 97 | switch inputKind { 98 | case Imm: SetVelocityC; 99 | case Reg: SetVelocityR; 100 | default: throw unsupported(); 101 | } 102 | case Add: 103 | switch inputKind { 104 | case Imm: AddVelocityC; 105 | case Reg: AddVelocityR; 106 | case Stack: AddVelocityS; 107 | default: throw unsupported(); 108 | } 109 | } 110 | case Length: 111 | switch operationType { 112 | case Set: 113 | switch inputKind { 114 | case Imm: SetSpeedC; 115 | case Reg: SetSpeedR; 116 | default: throw unsupported(); 117 | } 118 | case Add: 119 | switch inputKind { 120 | case Imm: AddSpeedC; 121 | case Reg: AddSpeedR; 122 | case Stack: AddSpeedS; 123 | default: throw unsupported(); 124 | } 125 | } 126 | case Angle: 127 | switch operationType { 128 | case Set: 129 | switch inputKind { 130 | case Imm: SetDirectionC; 131 | case Reg: SetDirectionR; 132 | default: throw unsupported(); 133 | } 134 | case Add: 135 | switch inputKind { 136 | case Imm: AddDirectionC; 137 | case Reg: AddDirectionR; 138 | case Stack: AddDirectionS; 139 | default: throw unsupported(); 140 | } 141 | } 142 | } 143 | case ShotPosition: 144 | switch _this.component { 145 | case Vector: 146 | switch operationType { 147 | case Set: 148 | switch inputKind { 149 | case Imm: SetShotPositionC; 150 | case Reg: SetShotPositionR; 151 | default: throw unsupported(); 152 | } 153 | case Add: 154 | switch inputKind { 155 | case Imm: AddShotPositionC; 156 | case Reg: AddShotPositionR; 157 | case Stack: AddShotPositionS; 158 | default: throw unsupported(); 159 | } 160 | } 161 | case Length: 162 | switch operationType { 163 | case Set: 164 | switch inputKind { 165 | case Imm: SetShotDistanceC; 166 | case Reg: SetShotDistanceR; 167 | default: throw unsupported(); 168 | } 169 | case Add: 170 | switch inputKind { 171 | case Imm: AddShotDistanceC; 172 | case Reg: AddShotDistanceR; 173 | case Stack: AddShotDistanceS; 174 | default: throw unsupported(); 175 | } 176 | } 177 | case Angle: 178 | switch operationType { 179 | case Set: 180 | switch inputKind { 181 | case Imm: SetShotBearingC; 182 | case Reg: SetShotBearingR; 183 | default: throw unsupported(); 184 | } 185 | case Add: 186 | switch inputKind { 187 | case Imm: AddShotBearingC; 188 | case Reg: AddShotBearingR; 189 | case Stack: AddShotBearingS; 190 | default: throw unsupported(); 191 | } 192 | } 193 | } 194 | case ShotVelocity: 195 | switch _this.component { 196 | case Vector: 197 | switch operationType { 198 | case Set: 199 | switch inputKind { 200 | case Imm: SetShotVelocityC; 201 | case Reg: SetShotVelocityR; 202 | default: throw unsupported(); 203 | } 204 | case Add: 205 | switch inputKind { 206 | case Imm: AddShotVelocityC; 207 | case Reg: AddShotVelocityR; 208 | case Stack: AddShotVelocityS; 209 | default: throw unsupported(); 210 | } 211 | } 212 | case Length: 213 | switch operationType { 214 | case Set: 215 | switch inputKind { 216 | case Imm: SetShotSpeedC; 217 | case Reg: SetShotSpeedR; 218 | default: throw unsupported(); 219 | } 220 | case Add: 221 | switch inputKind { 222 | case Imm: AddShotSpeedC; 223 | case Reg: AddShotSpeedR; 224 | case Stack: AddShotSpeedS; 225 | default: throw unsupported(); 226 | } 227 | } 228 | case Angle: 229 | switch operationType { 230 | case Set: 231 | switch inputKind { 232 | case Imm: SetShotDirectionC; 233 | case Reg: SetShotDirectionR; 234 | default: throw unsupported(); 235 | } 236 | case Add: 237 | switch inputKind { 238 | case Imm: AddShotDirectionC; 239 | case Reg: AddShotDirectionR; 240 | case Stack: AddShotDirectionS; 241 | default: throw unsupported(); 242 | } 243 | } 244 | } 245 | }); 246 | } 247 | 248 | public static function getDiffOpcode( 249 | _this: ActorProperty, 250 | inputKind: OperandKind 251 | ): Opcode { 252 | return Opcode.read(switch _this.type { 253 | case Position: 254 | switch _this.component { 255 | case Vector: 256 | switch inputKind { 257 | case Imm: GetDiffPositionCR; 258 | case Reg: GetDiffPositionRR; 259 | default: throw unsupported(); 260 | } 261 | case Length: 262 | switch inputKind { 263 | case Imm: GetDiffDistanceCR; 264 | case Reg: GetDiffDistanceRR; 265 | default: throw unsupported(); 266 | } 267 | case Angle: 268 | switch inputKind { 269 | case Imm: GetDiffBearingCR; 270 | case Reg: GetDiffBearingRR; 271 | default: throw unsupported(); 272 | } 273 | } 274 | case Velocity: 275 | switch _this.component { 276 | case Vector: 277 | switch inputKind { 278 | case Imm: GetDiffVelocityCR; 279 | case Reg: GetDiffVelocityRR; 280 | default: throw unsupported(); 281 | } 282 | case Length: 283 | switch inputKind { 284 | case Imm: GetDiffSpeedCR; 285 | case Reg: GetDiffSpeedRR; 286 | default: throw unsupported(); 287 | } 288 | case Angle: 289 | switch inputKind { 290 | case Imm: GetDiffDirectionCR; 291 | case Reg: GetDiffDirectionRR; 292 | default: throw unsupported(); 293 | } 294 | } 295 | case ShotPosition: 296 | switch _this.component { 297 | case Vector: 298 | switch inputKind { 299 | case Imm: GetDiffShotPositionCR; 300 | case Reg: GetDiffShotPositionRR; 301 | default: throw unsupported(); 302 | } 303 | case Length: 304 | switch inputKind { 305 | case Imm: GetDiffShotDistanceCR; 306 | case Reg: GetDiffShotDistanceRR; 307 | default: throw unsupported(); 308 | } 309 | case Angle: 310 | switch inputKind { 311 | case Imm: GetDiffShotBearingCR; 312 | case Reg: GetDiffShotBearingRR; 313 | default: throw unsupported(); 314 | } 315 | } 316 | case ShotVelocity: 317 | switch _this.component { 318 | case Vector: 319 | switch inputKind { 320 | case Imm: GetDiffShotVelocityCR; 321 | case Reg: GetDiffShotVelocityRR; 322 | default: throw unsupported(); 323 | } 324 | case Length: 325 | switch inputKind { 326 | case Imm: GetDiffShotSpeedCR; 327 | case Reg: GetDiffShotSpeedRR; 328 | default: throw unsupported(); 329 | } 330 | case Angle: 331 | switch inputKind { 332 | case Imm: GetDiffShotDirectionCR; 333 | case Reg: GetDiffShotDirectionRR; 334 | default: throw unsupported(); 335 | } 336 | } 337 | }); 338 | } 339 | 340 | static function unsupported(): String 341 | return "unsupported operation."; 342 | } 343 | 344 | private enum abstract ActorPropertyOperationType(Int) { 345 | final Set; 346 | final Add; 347 | } 348 | -------------------------------------------------------------------------------- /src/firedancer/assembly/types/ActorPropertyType.hx: -------------------------------------------------------------------------------- 1 | package firedancer.assembly.types; 2 | 3 | /** 4 | Type of actor's property to be operated. 5 | **/ 6 | enum abstract ActorPropertyType(Int) { 7 | final Position; 8 | final Velocity; 9 | final ShotPosition; 10 | final ShotVelocity; 11 | public function toString(): String { 12 | return switch (cast this : ActorPropertyType) { 13 | case Position: "position"; 14 | case Velocity: "velocity"; 15 | case ShotPosition: "shot_position"; 16 | case ShotVelocity: "shot_velocity"; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/firedancer/assembly/types/CastOperationType.hx: -------------------------------------------------------------------------------- 1 | package firedancer.assembly.types; 2 | 3 | enum abstract CastOperationType(Int) { 4 | final IntToFloat; 5 | final CartesianToVec; 6 | final PolarToVec; 7 | public function getOutputType(): ValueType { 8 | return switch (cast this : CastOperationType) { 9 | case IntToFloat: Float; 10 | case CartesianToVec: Vec; 11 | case PolarToVec: Vec; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/firedancer/assembly/types/EventType.hx: -------------------------------------------------------------------------------- 1 | package firedancer.assembly.types; 2 | 3 | enum abstract EventType(Int) { 4 | final Global; 5 | final Local; 6 | public function toString(): String { 7 | return switch (cast this : EventType) { 8 | case Global: "global"; 9 | case Local: "local"; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/firedancer/assembly/types/FireType.hx: -------------------------------------------------------------------------------- 1 | package firedancer.assembly.types; 2 | 3 | import firedancer.vm.FireArgument; 4 | import firedancer.vm.Constants.*; 5 | 6 | @:using(firedancer.assembly.types.FireType.FireTypeExtension) 7 | enum FireType { 8 | Simple; 9 | Complex(fireArgument: FireArgument); 10 | SimpleWithCode(fireCode: Int); 11 | ComplexWithCode(fireArgument: FireArgument, fireCode: Int); 12 | } 13 | 14 | class FireTypeExtension { 15 | public static function toString(_this: FireType): String { 16 | return switch _this { 17 | case Simple: "fire"; 18 | case Complex(fireArgument): 'fire ${fireArgument.toString()}'; 19 | case SimpleWithCode(fireCode): 'fire code $fireCode'; 20 | case ComplexWithCode(fireArgument, fireCode): 'fire ${fireArgument.toString()} code $fireCode'; 21 | } 22 | } 23 | 24 | public static function bytecodeLength(_this: FireType): UInt { 25 | return switch _this { 26 | case Simple: UInt.zero; 27 | case Complex(_): IntSize; 28 | case SimpleWithCode(_): IntSize; 29 | case ComplexWithCode(_, _): IntSize + IntSize; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/firedancer/assembly/types/TargetProperty.hx: -------------------------------------------------------------------------------- 1 | package firedancer.assembly.types; 2 | 3 | enum abstract TargetProperty(Int) { 4 | final Position; 5 | final X; 6 | final Y; 7 | final AngleFromShotPosition; 8 | 9 | /** 10 | @return `String` representation of `this`. 11 | **/ 12 | public function toString(): String { 13 | return switch (cast this : TargetProperty) { 14 | case Position: "position"; 15 | case X: "x"; 16 | case Y: "y"; 17 | case AngleFromShotPosition: "angle_from_shot_position"; 18 | } 19 | } 20 | 21 | public function getType(): ValueType { 22 | return switch (cast this : TargetProperty) { 23 | case Position: Vec; 24 | case X | Y: Float; 25 | case AngleFromShotPosition: Float; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/firedancer/import.hx: -------------------------------------------------------------------------------- 1 | package broker; 2 | 3 | import sinker.*; 4 | import sinker.globals.Globals.*; 5 | 6 | using sinker.extensions.Index; 7 | -------------------------------------------------------------------------------- /src/firedancer/script/Api.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script; 2 | 3 | import firedancer.vm.DebugCode; 4 | import firedancer.vm.ProgramPackage; 5 | import firedancer.assembly.AssemblyCodePackage; 6 | import firedancer.script.nodes.*; 7 | import firedancer.script.nodes.Duplicate; 8 | import firedancer.script.api_components.Position; 9 | import firedancer.script.api_components.Velocity; 10 | import firedancer.script.api_components.Shot; 11 | import firedancer.script.api_components.Random; 12 | import firedancer.script.expression.IntExpression; 13 | import firedancer.script.expression.FloatExpression; 14 | import firedancer.script.expression.AngleExpression; 15 | import firedancer.script.expression.IntLocalVariableExpression; 16 | import firedancer.script.expression.FloatLocalVariableExpression; 17 | import firedancer.script.expression.AngleLocalVariableExpression; 18 | 19 | class Api { 20 | /** 21 | Provides functions for operating position. 22 | **/ 23 | public static final position = new Position(); 24 | 25 | /** 26 | Provides functions for operating the length of position vector. 27 | **/ 28 | public static final distance = new Distance(); 29 | 30 | /** 31 | Provides functions for operating the angle of position vector. 32 | **/ 33 | public static final bearing = new Bearing(); 34 | 35 | /** 36 | Provides functions for operating velocity. 37 | **/ 38 | public static final velocity = new Velocity(); 39 | 40 | /** 41 | Provides functions for operating the length of velocity vector. 42 | **/ 43 | public static final speed = new Speed(); 44 | 45 | /** 46 | Provides functions for operating the angle of velocity vector. 47 | **/ 48 | public static final direction = new Direction(); 49 | 50 | /** 51 | Provides functions for operating shot position/velocity. 52 | **/ 53 | public static final shot = new Shot(); 54 | 55 | /** 56 | Provides functions for generating pseudorandom numbers. 57 | **/ 58 | public static final random = new Random(); 59 | 60 | /** 61 | Waits `frames`. 62 | **/ 63 | public static function wait(frames: IntExpression): Wait 64 | return new Wait(frames); 65 | 66 | /** 67 | Loops a given pattern endlessly. 68 | **/ 69 | public static function loop(ast: Ast): Loop 70 | return new Loop(ast); 71 | 72 | /** 73 | Repeats a given pattern `count` times. 74 | **/ 75 | public static function rep(count: IntExpression, ast: Ast): Repeat 76 | return new Repeat(ast, count); 77 | 78 | /** 79 | Duplicates a given pattern. 80 | @param count The repetition count. If the evaluated value is `0` or less, the result is unspecified. 81 | @param ast `fire()` at default. 82 | **/ 83 | public static function dup( 84 | count: IntExpression, 85 | params: DuplicateParameters, 86 | ?ast: Ast 87 | ): Duplicate { 88 | if (ast == null) ast = fire(); 89 | 90 | return new Duplicate(ast, count, params); 91 | } 92 | 93 | /** 94 | Emits a new actor with a pattern represented by the given `ast`. 95 | **/ 96 | public static function fire(?ast: Ast): Fire { 97 | return new Fire(Maybe.from(ast)); 98 | } 99 | 100 | /** 101 | Sets shot direction to the bearing to the target position. 102 | **/ 103 | public static function aim(): Aim { 104 | return new Aim(); 105 | } 106 | 107 | /** 108 | Runs `ast` every frame within the current node list. 109 | **/ 110 | public static function everyFrame(ast: Ast): EachFrame { 111 | return new EachFrame(ast); 112 | } 113 | 114 | /** 115 | Runs any pattern in another thread. 116 | 117 | The initial shot position/veocity are the same as that in the current thread, 118 | but any change to shot position/velocity does not affect other threads. 119 | **/ 120 | public static function async(ast: Ast): Async { 121 | return new Async(ast); 122 | } 123 | 124 | /** 125 | Runs the first pattern in the current thread and each subsequent one in a separate thread. 126 | Then waits until all patterns are completed. 127 | 128 | Any change to shot position/velocity made in a thread does not affect other threads. 129 | **/ 130 | public static function parallel(asts: Array): Parallel { 131 | return new Parallel(asts); 132 | } 133 | 134 | /** 135 | Ends running the bullet pattern with a specific end code. 136 | 137 | Normally the VM returns a default end code `0`. 138 | `end()` is useful for returning another end code so that you can 139 | branch the process according to that value. 140 | 141 | @param endCode The value to be returned from the VM. 142 | `0` for the default behavior, `-1` for killing the actor, or any other value for user-defined behaviors. 143 | @see `vanish()` 144 | **/ 145 | public static function end(endCode: Int): End { 146 | return new End(endCode); 147 | } 148 | 149 | /** 150 | Calls `end()` with a special end code `-1`, which should kill the actor itself. 151 | **/ 152 | public static function vanish(): End 153 | return end(-1); 154 | 155 | /** 156 | Refers to a local variable with `name` and interprets it as an integer. 157 | @param name Any string. 158 | However avoid using names which begins with double underscores `__` as they may be reserved for internal use. 159 | **/ 160 | public static function intVar(name: String): IntLocalVariableExpression 161 | return new IntLocalVariableExpression(name); 162 | 163 | /** 164 | Refers to a local variable with `name` and interprets it as a float. 165 | @param name Any string. 166 | However avoid using names which begins with double underscores `__` as they may be reserved for internal use. 167 | **/ 168 | public static function floatVar(name: String): FloatLocalVariableExpression 169 | return new FloatLocalVariableExpression(name); 170 | 171 | /** 172 | Refers to a local variable with `name` and interprets it as an angle. 173 | @param name Any string. 174 | However avoid using names which begins with double underscores `__` as they may be reserved for internal use. 175 | **/ 176 | public static function angleVar(name: String): AngleLocalVariableExpression 177 | return new AngleLocalVariableExpression(name); 178 | 179 | /** 180 | Calculates the trigonometric sine of `angle`. 181 | **/ 182 | public static function sin(angle: AngleExpression): FloatExpression 183 | return angle.sin(); 184 | 185 | /** 186 | Calculates the trigonometric cosine of `angle`. 187 | **/ 188 | public static function cos(angle: AngleExpression): FloatExpression 189 | return angle.cos(); 190 | 191 | /** 192 | Invokes a global event. 193 | @see `firedancer.types.EventHandler` 194 | **/ 195 | public static function event(globalEventCode: IntExpression): Event 196 | return new Event(Global, globalEventCode); 197 | 198 | /** 199 | Invokes a local event. 200 | @see `firedancer.types.EventHandler` 201 | **/ 202 | public static function localEvent(localEventCode: IntExpression): Event 203 | return new Event(Global, localEventCode); 204 | 205 | /** 206 | Runs debug process specified by `debugCode`. 207 | **/ 208 | public static function debug(debugCode: DebugCode): Debug 209 | return new Debug(debugCode); 210 | 211 | /** 212 | Inserts a comment. 213 | 214 | This is only used for debugging the assembly code. 215 | It has no effect when running the pattern because comments are removed when assembling into bytecode. 216 | **/ 217 | public static function comment(text: String): Comment 218 | return new Comment(text); 219 | 220 | /** 221 | Creates an `AssemblyCodePackage` instance that contains all `AssemblyCode` compiled. 222 | **/ 223 | @:access(firedancer.script.AstNode) 224 | public static function compileToAssembly( 225 | namedAstMap: Map, 226 | optimize = true 227 | ): AssemblyCodePackage { 228 | final compileContext = new CompileContext(); 229 | 230 | for (name => ast in namedAstMap) { 231 | final assemblyCode = ast.toAssembly(compileContext); 232 | compileContext.setNamedCode(assemblyCode, name); 233 | } 234 | 235 | var pkg = compileContext.createPackage(); 236 | if (optimize) pkg = pkg.optimize(); 237 | 238 | #if debug 239 | pkg.printAll(); 240 | #end 241 | 242 | return pkg; 243 | } 244 | 245 | /** 246 | Creates a `ProgramPackage` instance that contains all `Program` compiled & assembled. 247 | **/ 248 | public static function compile( 249 | namedAstMap: Map, 250 | optimize = true 251 | ): ProgramPackage { 252 | return compileToAssembly(namedAstMap, optimize).assemble(); 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /src/firedancer/script/ApiEx.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script; 2 | 3 | import firedancer.script.expression.IntExpression; 4 | import firedancer.script.expression.FloatExpression; 5 | import firedancer.script.expression.AngleExpression; 6 | import firedancer.script.nodes.*; 7 | 8 | /** 9 | Built-in shorthands/aliases using `Api` functions. 10 | **/ 11 | class ApiEx { 12 | /** 13 | Shorthand for repeating `ast` changing the shot direction until it circles around. 14 | @param ast `fire()` at default. 15 | **/ 16 | public static function radial(ways: IntExpression, ?ast: Ast): Ast { 17 | final loopCount = Api.intVar("__loopCnt"); 18 | final shotDirectionChangeRate = Api.angleVar("__sDirChgRt"); 19 | 20 | if (ast == null) ast = Api.fire(); 21 | 22 | return [ 23 | loopCount.let(ways), 24 | shotDirectionChangeRate.let((360.0 : AngleExpression) / loopCount), 25 | Api.rep(loopCount, [ 26 | ast, 27 | Api.shot.direction.add(shotDirectionChangeRate) 28 | ]) 29 | ]; 30 | } 31 | 32 | /** 33 | Shorthand for `dup()` with `shotDirectionRange`. 34 | 35 | This: 36 | 37 | ``` 38 | nWay(fire(), { ways: 3, angle: 30 }) 39 | ``` 40 | 41 | is the same as: 42 | 43 | ``` 44 | dup(fire(), { 45 | count: 3, 46 | shotDirectionRange: { start: -15, end: 15 } 47 | }) 48 | ``` 49 | 50 | @param ways The numer of ways. If the evaluated value is `0` or less, the result is unspecified. 51 | @param ast `fire()` at default. 52 | **/ 53 | public static function nWay( 54 | ways: IntExpression, 55 | params: { angle: AngleExpression }, 56 | ?ast: Ast 57 | ): Duplicate { 58 | final angle = params.angle; 59 | 60 | return Api.dup( 61 | ways, 62 | { shotDirectionRange: { start: -angle / 2, end: angle / 2 } }, 63 | ast 64 | ); 65 | } 66 | 67 | /** 68 | Alias for `dup()` with `shotSpeedChange`. 69 | @param count The repetition count. If the evaluated value is `0` or less, the result is unspecified. 70 | @param ast `fire()` at default. 71 | **/ 72 | public static function line( 73 | count: IntExpression, 74 | params: { shotSpeedChange: FloatExpression }, 75 | ?ast: Ast 76 | ): Duplicate { 77 | return Api.dup( 78 | count, 79 | { shotSpeedChange: params.shotSpeedChange }, 80 | ast 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/firedancer/script/Ast.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script; 2 | 3 | import firedancer.script.nodes.List; 4 | 5 | /** 6 | AST (abstract syntax tree) that represents a bullet hell pattern. 7 | **/ 8 | @:notNull @:forward 9 | abstract Ast(AstNode) from AstNode to AstNode { 10 | /** 11 | Converts `nodes` to `Ast`. 12 | **/ 13 | @:from public static inline function fromArray(nodes: Array): Ast 14 | return new List(nodes); 15 | 16 | /** 17 | Converts `nodes` to `Ast`. 18 | **/ 19 | @:from static extern inline function fromStdArray(nodes: std.Array): Ast 20 | return fromArray(cast nodes); 21 | } 22 | -------------------------------------------------------------------------------- /src/firedancer/script/AstNode.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script; 2 | 3 | import sneaker.exception.NotOverriddenException; 4 | import firedancer.assembly.AssemblyCode; 5 | import firedancer.script.nodes.Duplicate; 6 | import firedancer.script.expression.IntExpression; 7 | 8 | /** 9 | A node of AST (abstract syntax tree) that represents a bullet hell pattern. 10 | **/ 11 | class AstNode { 12 | var nodeType(default, null): AstNodeType = Other; 13 | 14 | /** 15 | Duplicates `this` pattern. Same effect as `Api.dup()`. 16 | @param count The repetition count. If the evaluated value is `0` or less, the result is unspecified. 17 | **/ 18 | public inline function dup(count: IntExpression, params: DuplicateParameters): Duplicate 19 | return Api.dup(count, params, this); 20 | 21 | /** 22 | Checks that `this` node contains any `Wait` element or kind of that. 23 | 24 | Used for detecting infinite loops, however note that this cannot detect 25 | zero or negative runtime value of waiting frames. 26 | 27 | @return `true` if `this` or any descendant node contains `Wait` element or kind of that. 28 | **/ 29 | function containsWait(): Bool 30 | throw new NotOverriddenException(); 31 | 32 | /** 33 | Converts the AST starting from `this` node into code in a virtual assembly language. 34 | **/ 35 | function toAssembly(context: CompileContext): AssemblyCode 36 | throw new NotOverriddenException(); 37 | } 38 | 39 | enum AstNodeType { 40 | EachFrame(astToBeInjected: Ast); 41 | Other; 42 | } 43 | -------------------------------------------------------------------------------- /src/firedancer/script/CompileContext.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script; 2 | 3 | import firedancer.assembly.Instruction; 4 | import firedancer.assembly.AssemblyCode; 5 | import firedancer.assembly.ValueType; 6 | import firedancer.assembly.AssemblyCodePackage; 7 | import firedancer.script.expression.GenericExpression; 8 | 9 | /** 10 | Context for compiling bullet patterns. 11 | **/ 12 | class CompileContext { 13 | /** 14 | Manages available local variables. 15 | **/ 16 | public final localVariables: LocalVariableTable; 17 | 18 | /** 19 | Stack for storing the label ID to be used for the next label. 20 | **/ 21 | public var nextLabelIdStack: Array = [UInt.zero]; 22 | 23 | /** 24 | List of `AssemblyCode` that should be able to retrieved by an `UInt` ID. 25 | **/ 26 | final codeList: Array = []; 27 | 28 | /** 29 | Mapping from names to ID numbers of `AssemblyCode` instances. 30 | **/ 31 | final nameIndexMap = new Map(); 32 | 33 | /** 34 | Stack of injection code. 35 | @see `pushInjectionCode()` 36 | **/ 37 | final injectionStack: Array = []; 38 | 39 | public function new() { 40 | this.localVariables = new LocalVariableTable(this); 41 | } 42 | 43 | /** 44 | Registers `code` in `this` context (if absent) 45 | so that it can be retrieved by a specific ID number. 46 | @return The ID for `code`. 47 | **/ 48 | public function setCode(code: AssemblyCode): UInt { 49 | final codeList = this.codeList; 50 | 51 | final existingId = codeList.indexOfFirst(cur -> cur.equals(code)); 52 | if (existingId.isSome()) return existingId.unwrap(); 53 | 54 | final id = codeList.length; 55 | codeList.push(code); 56 | 57 | return id; 58 | } 59 | 60 | /** 61 | Registers `code` in `this` context so that it can be retrieved by `name` as well as its ID. 62 | **/ 63 | public function setNamedCode(code: AssemblyCode, name: String): UInt { 64 | final index = this.setCode(code); 65 | 66 | final map = this.nameIndexMap; 67 | #if debug 68 | if (map.exists(name)) throw 'Duplicate pattern name: $name'; 69 | #end 70 | map.set(name, index); 71 | 72 | return index; 73 | } 74 | 75 | /** 76 | @return The entire injection code that have been pushed by `pushInjectionCode()`. 77 | The order is reversed so that the last pushed code comes first. 78 | @see `pushInjectionCode()` 79 | **/ 80 | public inline function getInjectionCode(): AssemblyCode { 81 | final code = this.injectionStack.copy(); 82 | code.reverse(); 83 | return code.flatten(); 84 | } 85 | 86 | /** 87 | Pushes `code` so that it is injected in every frame 88 | (i.e. before every `Break` operation or some sort of equivalent) 89 | within the current node list being compiled. 90 | @param code 91 | **/ 92 | public inline function pushInjectionCode(code: AssemblyCode): Void 93 | this.injectionStack.push(code); 94 | 95 | /** 96 | Pops the injection code that was previously pushed by `pushInjectionCode()`. 97 | **/ 98 | public inline function popInjectionCode(): Void 99 | this.injectionStack.pop(); 100 | 101 | /** 102 | Creates an `AssemblyCodePackage` instance. 103 | **/ 104 | public function createPackage(): AssemblyCodePackage { 105 | return { 106 | codeList: this.codeList, 107 | nameIndexMap: this.nameIndexMap 108 | }; 109 | } 110 | } 111 | 112 | class LocalVariableTable { 113 | /** 114 | Stack of `LocalVariable` instances. 115 | **/ 116 | final variableStack: Array; 117 | 118 | /** 119 | Stack for storing the size of `variableStack` at the beginning of each scope. 120 | **/ 121 | final variableCountStack: Array; 122 | 123 | final context: CompileContext; 124 | 125 | public function new(context: CompileContext) { 126 | this.variableStack = []; 127 | this.variableCountStack = []; 128 | this.context = context; 129 | } 130 | 131 | /** 132 | Starts a new scope. 133 | **/ 134 | public function startScope(): Void 135 | this.variableCountStack.push(this.variableStack.length); 136 | 137 | /** 138 | Ends the current scope. 139 | Pops all variables that were pushed after the last call of `startScope()`. 140 | @return `AssemblyCode` that frees variables that have been declared in the current scope. 141 | **/ 142 | public function endScope(): AssemblyCode { 143 | final maybeTargetSize = variableCountStack.pop(); 144 | if (maybeTargetSize.isNone()) throw "Called endScope() before startScope()."; 145 | final targetSize = maybeTargetSize.unwrap(); 146 | 147 | final freeInstructions: AssemblyCode = []; 148 | final variableStack = this.variableStack; 149 | while (targetSize < variableStack.length) { 150 | final variable = variableStack.pop().unwrap(); 151 | freeInstructions.push(Free(variable.name, variable.type)); 152 | } 153 | 154 | return freeInstructions; 155 | } 156 | 157 | /** 158 | Registers a local variable that is valid in the current scope. 159 | **/ 160 | public function push(name: String, type: ValueType): Void { 161 | this.variableStack.push({ 162 | name: name, 163 | type: type, 164 | context: this.context 165 | }); 166 | } 167 | 168 | /** 169 | @return `LocalVariable` that was declared in the narrowest scope. 170 | Throws error if a variable with `name` was never declared. 171 | **/ 172 | public function get(name: String): LocalVariable { 173 | final variableStack = this.variableStack; 174 | var i = variableStack.length.int() - 1; 175 | 176 | while (i >= 0) { 177 | final variable = variableStack[i]; 178 | if (variable.name == name) return variable; 179 | --i; 180 | } 181 | 182 | throw 'Unknown local variable: $name'; 183 | } 184 | } 185 | 186 | @:structInit 187 | class LocalVariable { 188 | public final name: String; 189 | public final type: ValueType; 190 | 191 | final context: CompileContext; 192 | 193 | /** 194 | Creates an `AssemblyCode` that assigns the value of `this` local variable 195 | to the int/float register. 196 | **/ 197 | public function load(): AssemblyCode { 198 | return switch this.type { 199 | case Int: [Load(Int(Var(this.name)))]; 200 | case Float: [Load(Float(Var(this.name)))]; 201 | case Vec: throw "Local variable of vector type is not supported."; 202 | }; 203 | } 204 | 205 | /** 206 | Creates an `AssemblyCode` that assigns `value` to the local variable specified by `this`. 207 | 208 | This does not check the type of `value` and it should be checked/determined before being passed. 209 | **/ 210 | public function setValue(value: GenericExpression): AssemblyCode { 211 | final store: Instruction = switch this.type { 212 | case Int: 213 | Store(Int(Reg), this.name); 214 | case Float: 215 | Store(Float(Reg), this.name); 216 | case Vec: 217 | throw "Local variable of vector type is not supported."; 218 | } 219 | 220 | return [ 221 | value.load(context), 222 | [store] 223 | ].flatten(); 224 | } 225 | 226 | /** 227 | Creates an `AssemblyCode` that adds `value` to the local variable specified by `this`. 228 | **/ 229 | public function addValue(value: GenericExpression): AssemblyCode { 230 | final store: Instruction = switch this.type { 231 | case Int: 232 | Add(Int(Var(this.name), Reg)); 233 | case Float: 234 | Add(Float(Var(this.name), Reg)); 235 | case Vec: 236 | throw "Local variable of vector type is not supported."; 237 | } 238 | 239 | return [ 240 | value.load(context), 241 | [store] 242 | ].flatten(); 243 | } 244 | 245 | /** 246 | Creates an `AssemblyCode` that increments the local variable specified by `this`. 247 | 248 | Available only if `this.type` is `Int`. 249 | **/ 250 | public function increment(): AssemblyCode { 251 | switch this.type { 252 | case Int: 253 | default: throw "Cannot increment local variable that is not an integer."; 254 | } 255 | 256 | return [Increment(this.name)]; 257 | } 258 | 259 | /** 260 | Creates an `AssemblyCode` that decrements the local variable specified by `this`. 261 | 262 | Available only if `this.type` is `Int`. 263 | **/ 264 | public function decrement(): AssemblyCode { 265 | switch this.type { 266 | case Int: 267 | default: throw "Cannot decrement local variable that is not an integer."; 268 | } 269 | 270 | return [Decrement(this.name)]; 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /src/firedancer/script/api_components/ActorPropertyApiComponent.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.api_components; 2 | 3 | import firedancer.assembly.types.ActorProperty; 4 | 5 | /** 6 | Base class for API component classes related to `ActorProperty`. 7 | **/ 8 | class ActorPropertyApiComponent { 9 | final property: ActorProperty; 10 | 11 | public function new(property: ActorProperty) 12 | this.property = property; 13 | } 14 | -------------------------------------------------------------------------------- /src/firedancer/script/api_components/Let.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.api_components; 2 | 3 | import firedancer.script.expression.IntExpression; 4 | import firedancer.script.nodes.DeclareLocalVariable; 5 | 6 | class Let { 7 | public function new() {} 8 | 9 | /** 10 | Declares a new local integer variable. 11 | @param name 12 | **/ 13 | public function int(name: String, ?initialValue: IntExpression): DeclareLocalVariable { 14 | return DeclareLocalVariable.fromInt(name, initialValue); 15 | } 16 | 17 | /** 18 | Declares a new local float variable. 19 | @param name 20 | **/ 21 | public function float( 22 | name: String, 23 | initialValue: FloatExpression 24 | ): DeclareLocalVariable { 25 | return DeclareLocalVariable.fromFloat(name, initialValue); 26 | } 27 | 28 | /** 29 | Declares a new local angle variable. 30 | @param name 31 | **/ 32 | public function angle( 33 | name: String, 34 | initialValue: AngleExpression 35 | ): DeclareLocalVariable { 36 | return DeclareLocalVariable.fromAngle(name, initialValue); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/firedancer/script/api_components/Position.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.api_components; 2 | 3 | import firedancer.script.expression.FloatLikeExpressionData; 4 | 5 | /** 6 | Provides features for operating actor's position. 7 | **/ 8 | class Position extends ActorPropertyApiComponent { 9 | /** 10 | Provides functions for operating position in cartesian coordinates. 11 | **/ 12 | public final cartesian = new CartesianPosition(); 13 | 14 | public function new() 15 | super({ type: Position, component: Vector }); 16 | 17 | /** 18 | Sets position to `(distance, bearing)`. 19 | **/ 20 | public inline function set(distance: FloatExpression, bearing: AngleExpression) { 21 | final vec: VecExpression = { length: distance, angle: bearing }; 22 | return new SetActorVector(Position, vec); 23 | } 24 | 25 | /** 26 | Adds a vector of `(distance, bearing)` to position. 27 | **/ 28 | public inline function add(distance: FloatExpression, bearing: AngleExpression) { 29 | final vec: VecExpression = { length: distance, angle: bearing }; 30 | return new AddActorProperty(Position, AddVector(vec)); 31 | } 32 | } 33 | 34 | /** 35 | Provides functions for operating actor's position in cartesian coordinates. 36 | **/ 37 | class CartesianPosition { 38 | public function new() {} 39 | 40 | /** 41 | Sets position to `(x, y)`. 42 | **/ 43 | public inline function set(x: FloatExpression, y: FloatExpression) { 44 | final vec: VecExpression = { x: x, y: y }; 45 | return new SetActorVector(Position, vec); 46 | } 47 | 48 | /** 49 | Adds `(x, y)` to position. 50 | **/ 51 | public inline function add(x: FloatExpression, y: FloatExpression) { 52 | final vec: VecExpression = { x: x, y: y }; 53 | return new AddActorProperty(Position, AddVector(vec)); 54 | } 55 | } 56 | 57 | /** 58 | Provides functions for operating the length of actor's position vector. 59 | **/ 60 | @:notNull @:forward 61 | abstract Distance(DistanceImpl) { 62 | public inline function new() 63 | this = new DistanceImpl(); 64 | 65 | @:access(firedancer.script.api_components.ActorPropertyApiComponent) 66 | @:to function toExpression(): FloatExpression { 67 | return 68 | FloatLikeExpressionEnum.Runtime(RuntimeExpressionEnum.Inst(Get(this.property))); 69 | } 70 | 71 | @:op(-A) 72 | inline function minus(): FloatExpression 73 | return -toExpression(); 74 | 75 | @:commutative @:op(A + B) 76 | static inline function addExpr(a: Distance, b: FloatExpression): FloatExpression 77 | return a.toExpression() + b; 78 | 79 | @:op(A - B) 80 | static inline function subtractExpr(a: Distance, b: FloatExpression): FloatExpression 81 | return a.toExpression() - b; 82 | 83 | @:commutative @:op(A * B) 84 | static inline function multiplyExpr(a: Distance, b: FloatExpression): FloatExpression 85 | return a.toExpression() * b; 86 | 87 | @:op(A / B) 88 | static inline function divideExpr(a: Distance, b: FloatExpression): FloatExpression 89 | return a.toExpression() / b; 90 | 91 | @:op(A % B) 92 | static inline function moduloExpr(a: Distance, b: FloatExpression): FloatExpression 93 | return a.toExpression() % b; 94 | } 95 | 96 | private class DistanceImpl extends ActorPropertyApiComponent { 97 | public function new() 98 | super({ type: Position, component: Length }); 99 | 100 | /** 101 | Sets the length of position vector to `value`. 102 | **/ 103 | public inline function set(value: FloatExpression) { 104 | return new SetActorProperty(Position, SetLength(value)); 105 | } 106 | 107 | /** 108 | Adds `value` to the length of position vector. 109 | **/ 110 | public inline function add(value: FloatExpression) { 111 | return new AddActorProperty(Position, AddLength(value)); 112 | } 113 | } 114 | 115 | /** 116 | Provides functions for operating the angle of actor's position vector. 117 | **/ 118 | @:notNull @:forward 119 | abstract Bearing(BearingImpl) { 120 | public inline function new() 121 | this = new BearingImpl(); 122 | 123 | @:access(firedancer.script.api_components.ActorPropertyApiComponent) 124 | @:to function toExpression(): AngleExpression { 125 | return 126 | FloatLikeExpressionEnum.Runtime(RuntimeExpressionEnum.Inst(Get(this.property))); 127 | } 128 | 129 | @:op(-A) 130 | inline function minus(): AngleExpression 131 | return -toExpression(); 132 | 133 | @:commutative @:op(A + B) 134 | static inline function addExpr(a: Bearing, b: AngleExpression): AngleExpression 135 | return a.toExpression() + b; 136 | 137 | @:op(A - B) 138 | static inline function subtractExpr(a: Bearing, b: AngleExpression): AngleExpression 139 | return a.toExpression() - b; 140 | 141 | @:commutative @:op(A * B) 142 | static inline function multiplyExpr(a: Bearing, b: FloatExpression): AngleExpression 143 | return a.toExpression() * b; 144 | 145 | @:op(A / B) 146 | static inline function divideExpr(a: Bearing, b: FloatExpression): AngleExpression 147 | return a.toExpression() / b; 148 | 149 | @:op(A % B) 150 | static inline function moduloExpr(a: Bearing, b: FloatExpression): AngleExpression 151 | return a.toExpression() % b; 152 | } 153 | 154 | private class BearingImpl extends ActorPropertyApiComponent { 155 | public function new() 156 | super({ type: Position, component: Angle }); 157 | 158 | /** 159 | Sets the angle of position vector to `value`. 160 | **/ 161 | public inline function set(value: AngleExpression) { 162 | return new SetActorProperty(Position, SetAngle(value)); 163 | } 164 | 165 | /** 166 | Adds `value` to the angle of position vector. 167 | **/ 168 | public inline function add(value: AngleExpression) { 169 | return new AddActorProperty(Position, AddAngle(value)); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/firedancer/script/api_components/Random.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.api_components; 2 | 3 | import firedancer.script.expression.IntExpression; 4 | import firedancer.script.expression.FloatExpression; 5 | 6 | /** 7 | Provides functions for generating pseudorandom numbers. 8 | **/ 9 | class Random { 10 | public function new() {} 11 | 12 | /** 13 | Provides functions for generating pseudorandom integer values. 14 | **/ 15 | public final int = new RandomInt(); 16 | 17 | /** 18 | Provides functions for generating pseudorandom angle values. 19 | **/ 20 | public final angle = new RandomAngle(); 21 | 22 | /** 23 | Gets a random value between `0` and `1`. 24 | **/ 25 | public inline function ratio(): FloatExpression 26 | return FloatExpression.fromEnum(Runtime(Inst(RandomRatio))); 27 | 28 | /** 29 | Gets a random value between `min` and `max`. 30 | **/ 31 | public inline function between( 32 | min: FloatExpression, 33 | max: FloatExpression 34 | ): FloatExpression { 35 | return min 36 | + FloatExpression.fromEnum(Runtime(UnaryOperation(Random(Float(Reg)), max - min))); 37 | } 38 | 39 | /** 40 | Gets a random value between `-max` and `max`. 41 | **/ 42 | public inline function signed(max: FloatExpression): FloatExpression { 43 | return 44 | FloatExpression.fromEnum(Runtime(UnaryOperation(RandomSigned(Float(Reg)), max))); 45 | } 46 | } 47 | 48 | class RandomAngle { 49 | public function new() {} 50 | 51 | /** 52 | Gets a random angle between `min` and `max`. 53 | **/ 54 | public inline function between( 55 | min: AngleExpression, 56 | max: AngleExpression 57 | ): AngleExpression { 58 | return min 59 | + AngleExpression.fromEnum(Runtime(UnaryOperation(Random(Float(Reg)), max - min))); 60 | } 61 | 62 | /** 63 | Gets a random angle between `-max` and `max`. 64 | **/ 65 | public inline function signed(max: AngleExpression): AngleExpression { 66 | return max.unaryOperation(RandomSigned(Float(Reg))); 67 | } 68 | 69 | /** 70 | Gets a random angle in range `[-centralAngle / 2, centralAngle / 2)`. 71 | 72 | Same effect as `random.signed(centralAngle / 2)`. 73 | **/ 74 | public inline function grouping(centralAngle: AngleExpression): AngleExpression 75 | return signed(centralAngle / 2); 76 | } 77 | 78 | class RandomInt { 79 | public function new() {} 80 | 81 | /** 82 | Gets a random angle between `min` and `max`. 83 | **/ 84 | public inline function between(min: IntExpression, max: IntExpression): IntExpression { 85 | return min 86 | + IntExpression.fromEnum(Runtime(UnaryOperation(Random(Int(Reg)), max - min))); 87 | } 88 | 89 | /** 90 | Gets a random angle between `-max` and `max`. 91 | **/ 92 | public inline function signed(max: IntExpression): IntExpression { 93 | return max.unaryOperation(RandomSigned(Int(Reg))); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/firedancer/script/api_components/Shot.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.api_components; 2 | 3 | import firedancer.script.api_components.ShotPosition; 4 | import firedancer.script.api_components.ShotVelocity; 5 | 6 | class Shot { 7 | public function new() {} 8 | 9 | /** 10 | Provides functions for operating shot position. 11 | **/ 12 | public final position = new ShotPosition(); 13 | 14 | /** 15 | Provides functions for operating the length of shot position vector. 16 | **/ 17 | public final distance = new ShotDistance(); 18 | 19 | /** 20 | Provides functions for operating the angle of shot position vector. 21 | **/ 22 | public final bearing = new ShotBearing(); 23 | 24 | /** 25 | Provides functions for operating shot velocity. 26 | **/ 27 | public final velocity = new ShotVelocity(); 28 | 29 | /** 30 | Provides functions for operating the length of shot velocity vector. 31 | **/ 32 | public final speed = new ShotSpeed(); 33 | 34 | /** 35 | Provides functions for operating the angle of shot velocity vector. 36 | **/ 37 | public final direction = new ShotDirection(); 38 | 39 | /** 40 | Angle from the current shot position to the current target position. 41 | **/ 42 | public final angleToTarget = AngleExpression.fromEnum(Runtime(Inst(GetTarget(AngleFromShotPosition)))); 43 | } 44 | -------------------------------------------------------------------------------- /src/firedancer/script/api_components/ShotPosition.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.api_components; 2 | 3 | import firedancer.script.expression.FloatLikeExpressionData; 4 | 5 | /** 6 | Provides features for operating actor's shot position. 7 | **/ 8 | class ShotPosition extends ActorPropertyApiComponent { 9 | /** 10 | Provides functions for operating shot position in cartesian coordinates. 11 | **/ 12 | public final cartesian = new CartesianShotPosition(); 13 | 14 | public function new() 15 | super({ type: ShotPosition, component: Vector }); 16 | 17 | /** 18 | Sets shot position to a vector of `(distance, bearing)`. 19 | **/ 20 | public inline function set(distance: FloatExpression, bearing: AngleExpression) { 21 | final vec: VecExpression = { length: distance, angle: bearing }; 22 | return new SetActorVector(ShotPosition, vec); 23 | } 24 | 25 | /** 26 | Adds a vector of `(distance, bearing)` to shot position. 27 | **/ 28 | public inline function add(distance: FloatExpression, bearing: AngleExpression) { 29 | final vec: VecExpression = { length: distance, angle: bearing }; 30 | return new AddActorProperty(ShotPosition, AddVector(vec)); 31 | } 32 | } 33 | 34 | /** 35 | Provides functions for operating actor's shot position in cartesian coordinates. 36 | **/ 37 | class CartesianShotPosition { 38 | public function new() {} 39 | 40 | /** 41 | Sets shot position to `(x, y)`. 42 | **/ 43 | public inline function set(x: FloatExpression, y: FloatExpression) { 44 | final vec: VecExpression = { x: x, y: y }; 45 | return new SetActorVector(ShotPosition, vec); 46 | } 47 | 48 | /** 49 | Adds `(x, y)` to shot position. 50 | **/ 51 | public inline function add(x: FloatExpression, y: FloatExpression) { 52 | final vec: VecExpression = { x: x, y: y }; 53 | return new AddActorProperty(ShotPosition, AddVector(vec)); 54 | } 55 | } 56 | 57 | /** 58 | Provides functions for operating the length of actor's shot position vector. 59 | **/ 60 | @:notNull @:forward 61 | abstract ShotDistance(ShotDistanceImpl) { 62 | public inline function new() 63 | this = new ShotDistanceImpl(); 64 | 65 | @:access(firedancer.script.api_components.ActorPropertyApiComponent) 66 | @:to function toExpression(): FloatExpression { 67 | return 68 | FloatLikeExpressionEnum.Runtime(RuntimeExpressionEnum.Inst(Get(this.property))); 69 | } 70 | 71 | @:op(-A) 72 | inline function minus(): FloatExpression 73 | return -toExpression(); 74 | 75 | @:commutative @:op(A + B) 76 | static inline function addExpr(a: ShotDistance, b: FloatExpression): FloatExpression 77 | return a.toExpression() + b; 78 | 79 | @:op(A - B) 80 | static inline function subtractExpr( 81 | a: ShotDistance, 82 | b: FloatExpression 83 | ): FloatExpression 84 | return a.toExpression() - b; 85 | 86 | @:commutative @:op(A * B) 87 | static inline function multiplyExpr( 88 | a: ShotDistance, 89 | b: FloatExpression 90 | ): FloatExpression 91 | return a.toExpression() * b; 92 | 93 | @:op(A / B) 94 | static inline function divideExpr(a: ShotDistance, b: FloatExpression): FloatExpression 95 | return a.toExpression() / b; 96 | 97 | @:op(A % B) 98 | static inline function moduloExpr(a: ShotDistance, b: FloatExpression): FloatExpression 99 | return a.toExpression() % b; 100 | } 101 | 102 | private class ShotDistanceImpl extends ActorPropertyApiComponent { 103 | public function new() 104 | super({ type: ShotPosition, component: Length }); 105 | 106 | /** 107 | Sets the length of shot position vector to `value`. 108 | **/ 109 | public inline function set(value: FloatExpression) { 110 | return new SetActorProperty(ShotPosition, SetLength(value)); 111 | } 112 | 113 | /** 114 | Adds `value` to the length of shot position vector. 115 | **/ 116 | public inline function add(value: FloatExpression) { 117 | return new AddActorProperty(ShotPosition, AddLength(value)); 118 | } 119 | } 120 | 121 | /** 122 | Provides functions for operating the angle of actor's shot position vector. 123 | **/ 124 | @:notNull @:forward 125 | abstract ShotBearing(ShotBearingImpl) { 126 | public inline function new() 127 | this = new ShotBearingImpl(); 128 | 129 | @:access(firedancer.script.api_components.ActorPropertyApiComponent) 130 | @:to function toExpression(): AngleExpression { 131 | return 132 | FloatLikeExpressionEnum.Runtime(RuntimeExpressionEnum.Inst(Get(this.property))); 133 | } 134 | 135 | @:op(-A) 136 | inline function minus(): AngleExpression 137 | return -toExpression(); 138 | 139 | @:commutative @:op(A + B) 140 | static inline function addExpr(a: ShotBearing, b: AngleExpression): AngleExpression 141 | return a.toExpression() + b; 142 | 143 | @:op(A - B) 144 | static inline function subtractExpr( 145 | a: ShotBearing, 146 | b: AngleExpression 147 | ): AngleExpression 148 | return a.toExpression() - b; 149 | 150 | @:commutative @:op(A * B) 151 | static inline function multiplyExpr( 152 | a: ShotBearing, 153 | b: FloatExpression 154 | ): AngleExpression 155 | return a.toExpression() * b; 156 | 157 | @:op(A / B) 158 | static inline function divideExpr(a: ShotBearing, b: FloatExpression): AngleExpression 159 | return a.toExpression() / b; 160 | 161 | @:op(A % B) 162 | static inline function moduloExpr(a: ShotBearing, b: FloatExpression): AngleExpression 163 | return a.toExpression() % b; 164 | } 165 | 166 | private class ShotBearingImpl extends ActorPropertyApiComponent { 167 | public function new() 168 | super({ type: ShotPosition, component: Angle }); 169 | 170 | /** 171 | Sets the angle of shot position vector to `value`. 172 | **/ 173 | public inline function set(value: AngleExpression) { 174 | return new SetActorProperty(ShotPosition, SetAngle(value)); 175 | } 176 | 177 | /** 178 | Adds `value` to the angle of shot position vector. 179 | **/ 180 | public inline function add(value: AngleExpression) { 181 | return new AddActorProperty(ShotPosition, AddAngle(value)); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/firedancer/script/api_components/ShotVelocity.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.api_components; 2 | 3 | import firedancer.script.expression.FloatLikeExpressionData; 4 | 5 | /** 6 | Provides features for operating actor's shot velocity. 7 | **/ 8 | class ShotVelocity extends ActorPropertyApiComponent { 9 | /** 10 | Provides functions for operating shot velocity in cartesian coordinates. 11 | **/ 12 | public final cartesian = new CartesianShotVelocity(); 13 | 14 | public function new() 15 | super({ type: ShotVelocity, component: Vector }); 16 | 17 | /** 18 | Sets shot velocity to a vector of `(speed, direction)`. 19 | **/ 20 | public inline function set(speed: FloatExpression, direction: AngleExpression) { 21 | final vec: VecExpression = { length: speed, angle: direction }; 22 | return new SetActorVector(ShotVelocity, vec); 23 | } 24 | 25 | /** 26 | Adds a vector of `(speed, direction)` to shot velocity. 27 | **/ 28 | public inline function add(speed: FloatExpression, direction: AngleExpression) { 29 | final vec: VecExpression = { length: speed, angle: direction }; 30 | return new AddActorProperty(ShotVelocity, AddVector(vec)); 31 | } 32 | } 33 | 34 | /** 35 | Provides functions for operating actor's shot velocity in cartesian coordinates. 36 | **/ 37 | class CartesianShotVelocity { 38 | public function new() {} 39 | 40 | /** 41 | Sets shot velocity to `(vx, vy)`. 42 | **/ 43 | public inline function set(vx: FloatExpression, vy: FloatExpression) { 44 | final vec: VecExpression = { x: vx, y: vy }; 45 | return new SetActorVector(ShotVelocity, vec); 46 | } 47 | 48 | /** 49 | Adds `(vx, vy)` to shot velocity. 50 | **/ 51 | public inline function add(vx: FloatExpression, vy: FloatExpression) { 52 | final vec: VecExpression = { x: vx, y: vy }; 53 | return new AddActorProperty(ShotVelocity, AddVector(vec)); 54 | } 55 | } 56 | 57 | /** 58 | Provides functions for operating the length of actor's shot velocity vector. 59 | **/ 60 | @:notNull @:forward 61 | abstract ShotSpeed(ShotSpeedImpl) { 62 | public inline function new() 63 | this = new ShotSpeedImpl(); 64 | 65 | @:access(firedancer.script.api_components.ActorPropertyApiComponent) 66 | @:to function toExpression(): FloatExpression { 67 | return 68 | FloatLikeExpressionEnum.Runtime(RuntimeExpressionEnum.Inst(Get(this.property))); 69 | } 70 | 71 | @:op(-A) 72 | inline function minus(): FloatExpression 73 | return -toExpression(); 74 | 75 | @:commutative @:op(A + B) 76 | static inline function addExpr(a: ShotSpeed, b: FloatExpression): FloatExpression 77 | return a.toExpression() + b; 78 | 79 | @:op(A - B) 80 | static inline function subtractExpr(a: ShotSpeed, b: FloatExpression): FloatExpression 81 | return a.toExpression() - b; 82 | 83 | @:commutative @:op(A * B) 84 | static inline function multiplyExpr(a: ShotSpeed, b: FloatExpression): FloatExpression 85 | return a.toExpression() * b; 86 | 87 | @:op(A / B) 88 | static inline function divideExpr(a: ShotSpeed, b: FloatExpression): FloatExpression 89 | return a.toExpression() / b; 90 | 91 | @:op(A % B) 92 | static inline function moduloExpr(a: ShotSpeed, b: FloatExpression): FloatExpression 93 | return a.toExpression() % b; 94 | } 95 | 96 | private class ShotSpeedImpl extends ActorPropertyApiComponent { 97 | public function new() 98 | super({ type: ShotVelocity, component: Length }); 99 | 100 | /** 101 | Sets the length of shot velocity vector to `value`. 102 | **/ 103 | public inline function set(value: FloatExpression) { 104 | return new SetActorProperty(ShotVelocity, SetLength(value)); 105 | } 106 | 107 | /** 108 | Adds `value` to the length of shot velocity vector. 109 | **/ 110 | public inline function add(value: FloatExpression) { 111 | return new AddActorProperty(ShotVelocity, AddLength(value)); 112 | } 113 | } 114 | 115 | /** 116 | Provides functions for operating the angle of actor's shot velocity vector. 117 | **/ 118 | @:notNull @:forward 119 | abstract ShotDirection(ShotDirectionImpl) { 120 | public inline function new() 121 | this = new ShotDirectionImpl(); 122 | 123 | @:access(firedancer.script.api_components.ActorPropertyApiComponent) 124 | @:to function toExpression(): AngleExpression { 125 | return 126 | FloatLikeExpressionEnum.Runtime(RuntimeExpressionEnum.Inst(Get(this.property))); 127 | } 128 | 129 | @:op(-A) 130 | inline function minus(): AngleExpression 131 | return -toExpression(); 132 | 133 | @:commutative @:op(A + B) 134 | static inline function addExpr(a: ShotDirection, b: AngleExpression): AngleExpression 135 | return a.toExpression() + b; 136 | 137 | @:op(A - B) 138 | static inline function subtractExpr( 139 | a: ShotDirection, 140 | b: AngleExpression 141 | ): AngleExpression 142 | return a.toExpression() - b; 143 | 144 | @:commutative @:op(A * B) 145 | static inline function multiplyExpr( 146 | a: ShotDirection, 147 | b: FloatExpression 148 | ): AngleExpression 149 | return a.toExpression() * b; 150 | 151 | @:op(A / B) 152 | static inline function divideExpr( 153 | a: ShotDirection, 154 | b: FloatExpression 155 | ): AngleExpression 156 | return a.toExpression() / b; 157 | 158 | @:op(A % B) 159 | static inline function moduloExpr( 160 | a: ShotDirection, 161 | b: FloatExpression 162 | ): AngleExpression 163 | return a.toExpression() % b; 164 | } 165 | 166 | private class ShotDirectionImpl extends ActorPropertyApiComponent { 167 | public function new() 168 | super({ type: ShotVelocity, component: Angle }); 169 | 170 | /** 171 | Sets the angle of shot velocity vector to `value`. 172 | **/ 173 | public inline function set(value: AngleExpression) { 174 | return new SetActorProperty(ShotVelocity, SetAngle(value)); 175 | } 176 | 177 | /** 178 | Adds `value` to the angle of shot velocity vector. 179 | **/ 180 | public inline function add(value: AngleExpression) { 181 | return new AddActorProperty(ShotVelocity, AddAngle(value)); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/firedancer/script/api_components/Velocity.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.api_components; 2 | 3 | import firedancer.script.expression.FloatLikeExpressionData; 4 | 5 | /** 6 | Provides features for operating actor's velocity. 7 | **/ 8 | class Velocity extends ActorPropertyApiComponent { 9 | /** 10 | Provides functions for operating velocity in cartesian coordinates. 11 | **/ 12 | public final cartesian = new CartesianVelocity(); 13 | 14 | public function new() 15 | super({ type: Velocity, component: Vector }); 16 | 17 | /** 18 | Sets velocity to `(speed, direction)`. 19 | **/ 20 | public inline function set(speed: FloatExpression, direction: AngleExpression) { 21 | final vec: VecExpression = { length: speed, angle: direction }; 22 | return new SetActorVector(Velocity, vec); 23 | } 24 | 25 | /** 26 | Adds a vector of `(speed, direction)` to velocity. 27 | **/ 28 | public inline function add(speed: FloatExpression, direction: AngleExpression) { 29 | final vec: VecExpression = { length: speed, angle: direction }; 30 | return new AddActorProperty(Velocity, AddVector(vec)); 31 | } 32 | } 33 | 34 | /** 35 | Provides functions for operating actor's velocity in cartesian coordinates. 36 | **/ 37 | class CartesianVelocity { 38 | public function new() {} 39 | 40 | /** 41 | Sets velocity to `(vx, vy)`. 42 | **/ 43 | public inline function set(vx: FloatExpression, vy: FloatExpression) { 44 | final vec: VecExpression = { x: vx, y: vy }; 45 | return new SetActorVector(Velocity, vec); 46 | } 47 | 48 | /** 49 | Adds `(vx, vy)` to velocity. 50 | **/ 51 | public inline function add(vx: FloatExpression, vy: FloatExpression) { 52 | final vec: VecExpression = { x: vx, y: vy }; 53 | return new AddActorProperty(Velocity, AddVector(vec)); 54 | } 55 | } 56 | 57 | /** 58 | Provides functions for operating the length of actor's velocity vector. 59 | **/ 60 | @:notNull @:forward 61 | abstract Speed(SpeedImpl) { 62 | public inline function new() 63 | this = new SpeedImpl(); 64 | 65 | @:access(firedancer.script.api_components.ActorPropertyApiComponent) 66 | @:to function toExpression(): FloatExpression { 67 | return 68 | FloatLikeExpressionEnum.Runtime(RuntimeExpressionEnum.Inst(Get(this.property))); 69 | } 70 | 71 | @:op(-A) 72 | inline function minus(): FloatExpression 73 | return -toExpression(); 74 | 75 | @:commutative @:op(A + B) 76 | static inline function addExpr(a: Speed, b: FloatExpression): FloatExpression 77 | return a.toExpression() + b; 78 | 79 | @:op(A - B) 80 | static inline function subtractExpr(a: Speed, b: FloatExpression): FloatExpression 81 | return a.toExpression() - b; 82 | 83 | @:commutative @:op(A * B) 84 | static inline function multiplyExpr(a: Speed, b: FloatExpression): FloatExpression 85 | return a.toExpression() * b; 86 | 87 | @:op(A / B) 88 | static inline function divideExpr(a: Speed, b: FloatExpression): FloatExpression 89 | return a.toExpression() / b; 90 | 91 | @:op(A % B) 92 | static inline function moduloExpr(a: Speed, b: FloatExpression): FloatExpression 93 | return a.toExpression() % b; 94 | } 95 | 96 | private class SpeedImpl extends ActorPropertyApiComponent { 97 | public function new() 98 | super({ type: Velocity, component: Length }); 99 | 100 | /** 101 | Sets the length of velocity vector to `value`. 102 | **/ 103 | public inline function set(value: FloatExpression) { 104 | return new SetActorProperty(Velocity, SetLength(value)); 105 | } 106 | 107 | /** 108 | Adds `value` to the length of velocity vector. 109 | **/ 110 | public inline function add(value: FloatExpression) { 111 | return new AddActorProperty(Velocity, AddLength(value)); 112 | } 113 | } 114 | 115 | /** 116 | Provides functions for operating the angle of actor's velocity vector. 117 | **/ 118 | @:notNull @:forward 119 | abstract Direction(DirectionImpl) { 120 | public inline function new() 121 | this = new DirectionImpl(); 122 | 123 | @:access(firedancer.script.api_components.ActorPropertyApiComponent) 124 | @:to function toExpression(): AngleExpression { 125 | return 126 | FloatLikeExpressionEnum.Runtime(RuntimeExpressionEnum.Inst(Get(this.property))); 127 | } 128 | 129 | @:op(-A) 130 | inline function minus(): AngleExpression 131 | return -toExpression(); 132 | 133 | @:commutative @:op(A + B) 134 | static inline function addExpr(a: Direction, b: AngleExpression): AngleExpression 135 | return a.toExpression() + b; 136 | 137 | @:op(A - B) 138 | static inline function subtractExpr(a: Direction, b: AngleExpression): AngleExpression 139 | return a.toExpression() - b; 140 | 141 | @:commutative @:op(A * B) 142 | static inline function multiplyExpr(a: Direction, b: FloatExpression): AngleExpression 143 | return a.toExpression() * b; 144 | 145 | @:op(A / B) 146 | static inline function divideExpr(a: Direction, b: FloatExpression): AngleExpression 147 | return a.toExpression() / b; 148 | 149 | @:op(A % B) 150 | static inline function moduloExpr(a: Direction, b: FloatExpression): AngleExpression 151 | return a.toExpression() % b; 152 | } 153 | 154 | private class DirectionImpl extends ActorPropertyApiComponent { 155 | public function new() 156 | super({ type: Velocity, component: Angle }); 157 | 158 | /** 159 | Sets the angle of velocity vector to `value`. 160 | **/ 161 | public inline function set(value: AngleExpression) { 162 | return new SetActorProperty(Velocity, SetAngle(value)); 163 | } 164 | 165 | /** 166 | Adds `value` to the angle of velocity vector. 167 | **/ 168 | public inline function add(value: AngleExpression) { 169 | return new AddActorProperty(Velocity, AddAngle(value)); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/firedancer/script/api_components/import.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.api_components; 2 | 3 | import firedancer.types.Azimuth; 4 | import firedancer.script.nodes.AddActorProperty; 5 | import firedancer.script.nodes.SetActorProperty; 6 | import firedancer.script.expression.IntExpression; 7 | import firedancer.script.expression.FloatExpression; 8 | import firedancer.script.expression.AngleExpression; 9 | import firedancer.script.expression.AngleExpression; 10 | import firedancer.script.expression.VecExpression; 11 | -------------------------------------------------------------------------------- /src/firedancer/script/expression/AngleExpression.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.expression; 2 | 3 | import reckoner.Geometry.DEGREES_TO_RADIANS; 4 | import firedancer.types.Angle; 5 | import firedancer.script.expression.FloatLikeExpressionData; 6 | 7 | /** 8 | Abstract over `FloatLikeExpressionData` that can be implicitly converted from `Angle`. 9 | **/ 10 | @:notNull @:forward 11 | abstract AngleExpression( 12 | FloatLikeExpressionData 13 | ) from FloatLikeExpressionData to FloatLikeExpressionData { 14 | /** 15 | The factor by which the constant values should be multiplied when writing into `AssemblyCode`. 16 | **/ 17 | public static extern inline final constantFactor = DEGREES_TO_RADIANS; 18 | 19 | @:from public static extern inline function fromEnum( 20 | data: FloatLikeExpressionEnum 21 | ): AngleExpression { 22 | return FloatLikeExpressionData.create(data); 23 | } 24 | 25 | @:from public static extern inline function fromAngle(value: Angle): AngleExpression { 26 | return FloatLikeExpressionData.create(FloatLikeExpressionEnum.Constant({ 27 | value: value.toDegrees(), 28 | factor: constantFactor 29 | })); 30 | } 31 | 32 | @:from static extern inline function fromFloat(value: Float): AngleExpression 33 | return fromAngle(value); 34 | 35 | @:from static extern inline function fromInt(value: Int): AngleExpression 36 | return fromFloat(value); 37 | 38 | @:op(-A) 39 | extern inline function unaryMinus(): AngleExpression 40 | return this.unaryMinus(); 41 | 42 | @:op(A + B) 43 | static extern inline function add( 44 | a: AngleExpression, 45 | b: AngleExpression 46 | ): AngleExpression { 47 | return a.add(b); 48 | } 49 | 50 | @:op(A - B) 51 | static extern inline function subtract( 52 | a: AngleExpression, 53 | b: AngleExpression 54 | ): AngleExpression { 55 | return a.subtract(b); 56 | } 57 | 58 | @:op(A * B) @:commutative 59 | static extern inline function multiply( 60 | expr: AngleExpression, 61 | factor: FloatExpression 62 | ): AngleExpression { 63 | return expr.multiply(factor); 64 | } 65 | 66 | @:op(A / B) 67 | static extern inline function divide( 68 | a: AngleExpression, 69 | b: FloatExpression 70 | ): AngleExpression { 71 | return a.divide(b); 72 | } 73 | 74 | @:op(A % B) 75 | static extern inline function modulo( 76 | a: AngleExpression, 77 | b: FloatExpression 78 | ): AngleExpression { 79 | return a.modulo(b); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/firedancer/script/expression/AngleLocalVariableExpression.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.expression; 2 | 3 | import firedancer.script.nodes.DeclareLocalVariable; 4 | import firedancer.script.nodes.OperateLocalVariable; 5 | 6 | @:access(firedancer.script.expression.AngleExpression) 7 | abstract AngleLocalVariableExpression(String) { 8 | public extern inline function new(name: String) 9 | this = name; 10 | 11 | @:to function toAngleExpression(): AngleExpression { 12 | return AngleExpression.fromEnum(FloatLikeExpressionEnum.Runtime(Custom(context -> { 13 | final variable = context.localVariables.get(this); 14 | final code = variable.load(); 15 | switch variable.type { 16 | case Int: 17 | code.push(Cast(IntToFloat)); 18 | code.push( 19 | Mult(Float(RegBuf), Float(Imm(AngleExpression.constantFactor))) 20 | ); 21 | case Float: 22 | case Vec: throw "Cannot cast vector to float."; 23 | } 24 | code; 25 | }))); 26 | } 27 | 28 | @:to public function toString(): String 29 | return 'AngleVar($this)'; 30 | 31 | @:op(-A) 32 | extern inline function unaryMinus(): AngleExpression 33 | return get().unaryMinus(); 34 | 35 | @:op(A + B) @:commutative 36 | static extern inline function addOp( 37 | a: AngleLocalVariableExpression, 38 | b: AngleExpression 39 | ): AngleExpression { 40 | return a.get().add(b); 41 | } 42 | 43 | @:op(A - B) 44 | static extern inline function subtract( 45 | a: AngleLocalVariableExpression, 46 | b: AngleExpression 47 | ): AngleExpression { 48 | return a.get().subtract(b); 49 | } 50 | 51 | @:op(A - B) 52 | static extern inline function subtractR( 53 | a: AngleExpression, 54 | b: AngleLocalVariableExpression 55 | ): AngleExpression { 56 | return a.subtract(b); 57 | } 58 | 59 | @:op(A * B) @:commutative 60 | static extern inline function multiply( 61 | a: AngleLocalVariableExpression, 62 | b: FloatExpression 63 | ): AngleExpression { 64 | return a.get().multiply(b); 65 | } 66 | 67 | @:op(A / B) 68 | static extern inline function divide( 69 | a: AngleLocalVariableExpression, 70 | b: FloatExpression 71 | ): AngleExpression { 72 | return a.get().divide(b); 73 | } 74 | 75 | @:op(A % B) 76 | static extern inline function modulo( 77 | a: AngleLocalVariableExpression, 78 | b: AngleExpression 79 | ): AngleExpression { 80 | return a.get().modulo(b); 81 | } 82 | 83 | @:op(A % B) 84 | static extern inline function moduloR( 85 | a: AngleExpression, 86 | b: AngleLocalVariableExpression 87 | ): AngleExpression { 88 | return a.modulo(b); 89 | } 90 | 91 | /** 92 | Declares `this` local variable so that it can be used in the current scope. 93 | **/ 94 | public function let(?initialValue: AngleExpression): DeclareLocalVariable { 95 | return DeclareLocalVariable.fromAngle(this, initialValue); 96 | } 97 | 98 | /** 99 | Assigns `value` to `this` local variable. 100 | **/ 101 | public function set(value: AngleExpression): OperateLocalVariable 102 | return { name: this, value: value, operation: Set }; 103 | 104 | /** 105 | Adds `value` to `this` local variable. 106 | **/ 107 | public function add(value: AngleExpression): OperateLocalVariable 108 | return { name: this, value: value, operation: Add }; 109 | 110 | function get() 111 | return toAngleExpression(); 112 | } 113 | -------------------------------------------------------------------------------- /src/firedancer/script/expression/ExpressionData.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.expression; 2 | 3 | import firedancer.assembly.Instruction; 4 | import firedancer.assembly.AssemblyCode; 5 | 6 | interface ExpressionData { 7 | /** 8 | Creates an `AssemblyCode` that assigns `this` value to the register. 9 | **/ 10 | public function load(context: CompileContext): AssemblyCode; 11 | 12 | /** 13 | Creates an `AssemblyCode` that runs `instruction` receiving `this` value as argument. 14 | **/ 15 | public function use(context: CompileContext, instruction: Instruction): AssemblyCode; 16 | } 17 | -------------------------------------------------------------------------------- /src/firedancer/script/expression/FloatExpression.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.expression; 2 | 3 | import firedancer.script.expression.FloatLikeExpressionData; 4 | 5 | /** 6 | Abstract over `FloatLikeExpressionData` that can be implicitly cast from `Float`. 7 | **/ 8 | @:notNull @:forward 9 | abstract FloatExpression( 10 | FloatLikeExpressionData 11 | ) from FloatLikeExpressionData to FloatLikeExpressionData { 12 | /** 13 | The factor by which the constant values should be multiplied when writing into `AssemblyCode`. 14 | **/ 15 | public static extern inline final constantFactor = 1.0; 16 | 17 | @:from public static extern inline function fromEnum( 18 | data: FloatLikeExpressionEnum 19 | ): FloatExpression { 20 | return FloatLikeExpressionData.create(data); 21 | } 22 | 23 | @:from static extern inline function fromFloat(value: Float): FloatExpression { 24 | return FloatLikeExpressionData.create(FloatLikeExpressionEnum.Constant({ 25 | value: value, 26 | factor: constantFactor 27 | })); 28 | } 29 | 30 | @:from static extern inline function fromInt(value: Int): FloatExpression 31 | return fromFloat(value); 32 | 33 | @:op(-A) 34 | extern inline function unaryMinus(): FloatExpression 35 | return this.unaryMinus(); 36 | 37 | @:op(A + B) 38 | static extern inline function add( 39 | a: FloatExpression, 40 | b: FloatExpression 41 | ): FloatExpression { 42 | return a.add(b); 43 | } 44 | 45 | @:op(A - B) 46 | static extern inline function subtract( 47 | a: FloatExpression, 48 | b: FloatExpression 49 | ): FloatExpression { 50 | return a.subtract(b); 51 | } 52 | 53 | @:op(A * B) 54 | static extern inline function multiply( 55 | a: FloatExpression, 56 | b: FloatExpression 57 | ): FloatExpression { 58 | return a.multiply(b); 59 | } 60 | 61 | @:op(A / B) 62 | static extern inline function divide( 63 | a: FloatExpression, 64 | b: FloatExpression 65 | ): FloatExpression { 66 | return a.divide(b); 67 | } 68 | 69 | @:op(A % B) 70 | static extern inline function modulo( 71 | a: FloatExpression, 72 | b: FloatExpression 73 | ): FloatExpression { 74 | return a.modulo(b); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/firedancer/script/expression/FloatLikeExpressionData.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.expression; 2 | 3 | import firedancer.assembly.Instruction; 4 | import firedancer.assembly.AssemblyCode; 5 | import firedancer.script.expression.subtypes.FloatLikeConstant; 6 | import firedancer.script.expression.subtypes.FloatLikeRuntimeExpression; 7 | 8 | /** 9 | Expression representing any float value. 10 | **/ 11 | enum FloatLikeExpressionEnum { 12 | Constant(value: FloatLikeConstant); 13 | Runtime(expression: FloatLikeRuntimeExpression); 14 | } 15 | 16 | /** 17 | Wrapper of `FloatLikeExpressionEnum`. 18 | **/ 19 | @:structInit 20 | class FloatLikeExpressionData implements ExpressionData { 21 | public static extern inline function create( 22 | data: FloatLikeExpressionEnum 23 | ): FloatLikeExpressionData { 24 | return { data: data }; 25 | } 26 | 27 | /** 28 | The enum instance. 29 | **/ 30 | public final data: FloatLikeExpressionEnum; 31 | 32 | /** 33 | Creates an `AssemblyCode` that assigns `this` value to the float register. 34 | **/ 35 | public function load(context: CompileContext): AssemblyCode { 36 | return switch this.data { 37 | case Constant(value): 38 | Load(Float(Imm(value.toImmediateValue()))); 39 | case Runtime(expression): 40 | expression.load(context); 41 | } 42 | } 43 | 44 | /** 45 | Creates an `AssemblyCode` that runs `instruction` 46 | receiving `this` value as argument via register. 47 | **/ 48 | public function use(context: CompileContext, instruction: Instruction): AssemblyCode { 49 | return [ 50 | load(context), 51 | [instruction] 52 | ].flatten(); 53 | } 54 | 55 | public function tryGetConstant(): Maybe { 56 | return switch this.data { 57 | case Constant(value): 58 | Maybe.from(value.toImmediateValue()); 59 | case Runtime(expression): 60 | Maybe.none(); 61 | } 62 | } 63 | 64 | public function unaryOperation(instruction: Instruction): FloatLikeExpressionData { 65 | return create( 66 | Runtime( 67 | FloatLikeRuntimeExpressionEnum.UnaryOperation( 68 | instruction, 69 | this 70 | ) 71 | ) 72 | ); 73 | } 74 | 75 | public function binaryOperation( 76 | instruction: Instruction, 77 | other: FloatLikeExpressionData 78 | ): FloatLikeExpressionData { 79 | return create( 80 | Runtime( 81 | FloatLikeRuntimeExpressionEnum.BinaryOperation( 82 | instruction, 83 | this, 84 | other 85 | ) 86 | ) 87 | ); 88 | } 89 | 90 | public function unaryMinus(): FloatLikeExpressionData 91 | return unaryOperation(Minus(Float(Reg))); 92 | 93 | public function cos(): FloatLikeExpressionData 94 | return unaryOperation(Cos); 95 | 96 | public function sin(): FloatLikeExpressionData 97 | return unaryOperation(Sin); 98 | 99 | public function add(other: FloatLikeExpressionData): FloatLikeExpressionData 100 | return binaryOperation(Add(Float(RegBuf, Reg)), other); 101 | 102 | public function subtract(other: FloatLikeExpressionData): FloatLikeExpressionData 103 | return binaryOperation(Sub(Float(RegBuf, Reg)), other); 104 | 105 | public function multiply(other: FloatLikeExpressionData): FloatLikeExpressionData 106 | return binaryOperation(Mult(Float(RegBuf), Float(Reg)), other); 107 | 108 | public function divide(other: FloatLikeExpressionData): FloatLikeExpressionData 109 | return binaryOperation(Div(Float(RegBuf), Float(Reg)), other); 110 | 111 | public function modulo(other: FloatLikeExpressionData): FloatLikeExpressionData 112 | return binaryOperation(Mod(Float(RegBuf), Float(Reg)), other); 113 | 114 | public extern inline function toEnum(): FloatLikeExpressionEnum 115 | return this.data; 116 | 117 | public function toString(): String { 118 | return switch data { 119 | case Constant(value): 'FloatC(${value.toString()})'; 120 | case Runtime(expression): 'FloatR(${expression.toString()})'; 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/firedancer/script/expression/FloatLocalVariableExpression.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.expression; 2 | 3 | import firedancer.script.nodes.DeclareLocalVariable; 4 | import firedancer.script.nodes.OperateLocalVariable; 5 | 6 | @:access(firedancer.script.expression.FloatExpression) 7 | abstract FloatLocalVariableExpression(String) { 8 | public extern inline function new(name: String) 9 | this = name; 10 | 11 | @:to function toFloatExpression(): FloatExpression { 12 | return FloatExpression.fromEnum(FloatLikeExpressionEnum.Runtime(Custom(context -> { 13 | final variable = context.localVariables.get(this); 14 | final code = variable.load(); 15 | switch variable.type { 16 | case Int: code.push(Cast(IntToFloat)); 17 | case Float: 18 | case Vec: throw "Cannot cast vector to float."; 19 | } 20 | code; 21 | }))); 22 | } 23 | 24 | @:to public function toString(): String 25 | return 'FloatRar($this)'; 26 | 27 | @:op(-A) 28 | extern inline function unaryMinus(): FloatExpression 29 | return get().unaryMinus(); 30 | 31 | @:op(A + B) @:commutative 32 | static extern inline function addOp( 33 | a: FloatLocalVariableExpression, 34 | b: FloatExpression 35 | ): FloatExpression { 36 | return a.get().add(b); 37 | } 38 | 39 | @:op(A - B) 40 | static extern inline function subtract( 41 | a: FloatLocalVariableExpression, 42 | b: FloatExpression 43 | ): FloatExpression { 44 | return a.get().subtract(b); 45 | } 46 | 47 | @:op(A - B) 48 | static extern inline function subtractR( 49 | a: FloatExpression, 50 | b: FloatLocalVariableExpression 51 | ): FloatExpression { 52 | return a.subtract(b); 53 | } 54 | 55 | @:op(A * B) @:commutative 56 | static extern inline function multiply( 57 | a: FloatLocalVariableExpression, 58 | b: FloatExpression 59 | ): FloatExpression { 60 | return a.get().multiply(b); 61 | } 62 | 63 | @:op(A / B) 64 | static extern inline function divide( 65 | a: FloatLocalVariableExpression, 66 | b: FloatExpression 67 | ): FloatExpression { 68 | return a.get().divide(b); 69 | } 70 | 71 | @:op(A / B) 72 | static extern inline function divideR( 73 | a: FloatExpression, 74 | b: FloatLocalVariableExpression 75 | ): FloatExpression { 76 | return a.divide(b); 77 | } 78 | 79 | @:op(A % B) 80 | static extern inline function modulo( 81 | a: FloatLocalVariableExpression, 82 | b: FloatExpression 83 | ): FloatExpression { 84 | return a.get().modulo(b); 85 | } 86 | 87 | @:op(A % B) 88 | static extern inline function moduloR( 89 | a: FloatExpression, 90 | b: FloatLocalVariableExpression 91 | ): FloatExpression { 92 | return a.modulo(b); 93 | } 94 | 95 | /** 96 | Declares `this` local variable so that it can be used in the current scope. 97 | **/ 98 | public function let(?initialValue: FloatExpression): DeclareLocalVariable { 99 | return DeclareLocalVariable.fromFloat(this, initialValue); 100 | } 101 | 102 | /** 103 | Assigns `value` to `this` local variable. 104 | **/ 105 | public function set(value: FloatExpression): OperateLocalVariable 106 | return { name: this, value: value, operation: Set }; 107 | 108 | /** 109 | Adds `value` to `this` local variable. 110 | **/ 111 | public function add(value: FloatExpression): OperateLocalVariable 112 | return { name: this, value: value, operation: Add }; 113 | 114 | function get() 115 | return toFloatExpression(); 116 | } 117 | -------------------------------------------------------------------------------- /src/firedancer/script/expression/GenericExpression.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.expression; 2 | 3 | import firedancer.assembly.Instruction; 4 | import firedancer.assembly.AssemblyCode; 5 | 6 | abstract GenericExpression(Data) from Data { 7 | @:from 8 | static extern inline function fromIntExpr(expr: IntExpression): GenericExpression 9 | return IntExpr(expr); 10 | 11 | @:from 12 | static extern inline function fromFloatExpr(expr: FloatExpression): GenericExpression 13 | return FloatExpr(expr); 14 | 15 | @:from 16 | static extern inline function fromAngleExpr(expr: AngleExpression): GenericExpression 17 | return AngleExpr(expr); 18 | 19 | @:from 20 | static extern inline function fromVecExpr(expr: VecExpression): GenericExpression 21 | return VecExpr(expr); 22 | 23 | @:to function toIntExpr(): IntExpression { 24 | return switch this { 25 | case IntExpr(expr): expr; 26 | case FloatExpr(_): throw "Cannot cast FloatExpression to IntExpression."; 27 | case AngleExpr(_): throw "Cannot cast AngleExpression to IntExpression."; 28 | case VecExpr(_): throw "Cannot cast VecExpression to IntExpression."; 29 | } 30 | } 31 | 32 | @:to function toFloatExpr(): FloatExpression { 33 | return switch this { 34 | case IntExpr(expr): expr; 35 | case FloatExpr(expr): expr; 36 | case AngleExpr(_): throw "Cannot cast AngleExpression to FloatExpression."; 37 | case VecExpr(_): throw "Cannot cast VecExpression to FloatExpression."; 38 | } 39 | } 40 | 41 | @:to function toAngleExpr(): AngleExpression { 42 | return switch this { 43 | case IntExpr(expr): expr; 44 | case FloatExpr(_): throw "Cannot cast AngleExpression to FloatExpression."; 45 | case AngleExpr(expr): expr; 46 | case VecExpr(_): throw "Cannot cast VecExpression to AngleExpression."; 47 | } 48 | } 49 | 50 | @:to function toVecExpr(): VecExpression { 51 | return switch this { 52 | case IntExpr(_): throw "Cannot cast IntExpression to VecExpression."; 53 | case FloatExpr(_): throw "Cannot cast FloatExpression to VecExpression."; 54 | case AngleExpr(_): throw "Cannot cast AngleExpression to VecExpression."; 55 | case VecExpr(expr): expr; 56 | } 57 | } 58 | 59 | /** 60 | Creates an `AssemblyCode` that assigns `this` value to the register. 61 | **/ 62 | public function load(context: CompileContext): AssemblyCode { 63 | return switch this { 64 | case IntExpr(expr): expr.load(context); 65 | case FloatExpr(expr): expr.load(context); 66 | case AngleExpr(expr): expr.load(context); 67 | case VecExpr(expr): expr.load(context); 68 | } 69 | } 70 | 71 | /** 72 | Creates an `AssemblyCode` that runs `instruction` 73 | receiving `this` value as argument. 74 | **/ 75 | public function use(context: CompileContext, instruction: Instruction): AssemblyCode { 76 | return switch this { 77 | case IntExpr(expr): expr.use(context, instruction); 78 | case FloatExpr(expr): expr.use(context, instruction); 79 | case AngleExpr(expr): expr.use(context, instruction); 80 | case VecExpr(expr): expr.use(context, instruction); 81 | } 82 | } 83 | 84 | public extern inline function toEnum(): Data 85 | return this; 86 | } 87 | 88 | private enum Data { 89 | IntExpr(expr: IntExpression); 90 | FloatExpr(expr: FloatExpression); 91 | AngleExpr(expr: AngleExpression); 92 | VecExpr(expr: VecExpression); 93 | } 94 | -------------------------------------------------------------------------------- /src/firedancer/script/expression/IntExpression.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.expression; 2 | 3 | import firedancer.script.expression.IntLikeExpressionData; 4 | 5 | /** 6 | Abstract over `IntLikeExpressionData` that can be implicitly cast from `UInt`. 7 | **/ 8 | @:notNull @:forward 9 | abstract IntExpression( 10 | IntLikeExpressionData 11 | ) from IntLikeExpressionData to IntLikeExpressionData { 12 | @:from public static extern inline function fromInt(value: Int): IntExpression 13 | return IntLikeExpressionData.create(IntLikeExpressionEnum.Constant(value)); 14 | 15 | @:from public static extern inline function fromEnum( 16 | data: IntLikeExpressionEnum 17 | ): IntExpression { 18 | return IntLikeExpressionData.create(data); 19 | } 20 | 21 | @:to extern inline function toFloatExpression(): FloatExpression 22 | return this.toFloatExpression(); 23 | 24 | @:to extern inline function toAngleExpression(): AngleExpression 25 | return this.toAngleExpression(); 26 | 27 | @:op(-A) 28 | extern inline function unaryMinus(): IntExpression 29 | return this.unaryMinus(); 30 | 31 | @:op(A + B) 32 | static extern inline function add(a: IntExpression, b: IntExpression): IntExpression { 33 | return a.add(b); 34 | } 35 | 36 | @:op(A - B) 37 | static extern inline function subtract( 38 | a: IntExpression, 39 | b: IntExpression 40 | ): IntExpression { 41 | return a.subtract(b); 42 | } 43 | 44 | @:op(A * B) 45 | static extern inline function multiply( 46 | a: IntExpression, 47 | b: IntExpression 48 | ): IntExpression { 49 | return a.multiply(b); 50 | } 51 | 52 | @:op(A / B) 53 | static extern inline function divide( 54 | a: IntExpression, 55 | b: IntExpression 56 | ): IntExpression { 57 | return a.divide(b); 58 | } 59 | 60 | @:op(A % B) 61 | static extern inline function modulo( 62 | a: IntExpression, 63 | b: IntExpression 64 | ): IntExpression { 65 | return a.modulo(b); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/firedancer/script/expression/IntLikeExpressionData.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.expression; 2 | 3 | import firedancer.assembly.Instruction; 4 | import firedancer.assembly.AssemblyCode; 5 | import firedancer.script.expression.FloatLikeExpressionData; 6 | import firedancer.script.expression.subtypes.IntLikeRuntimeExpression; 7 | import firedancer.script.expression.subtypes.IntLikeConstant; 8 | import firedancer.script.expression.subtypes.FloatLikeRuntimeExpression; 9 | 10 | enum IntLikeExpressionEnum { 11 | Constant(value: IntLikeConstant); 12 | Runtime(expression: IntLikeRuntimeExpression); 13 | } 14 | 15 | /** 16 | Expression representing any float value. 17 | **/ 18 | @:structInit 19 | class IntLikeExpressionData implements ExpressionData { 20 | public static extern inline function create( 21 | data: IntLikeExpressionEnum 22 | ): IntLikeExpressionData { 23 | return { data: data }; 24 | } 25 | 26 | public final data: IntLikeExpressionEnum; 27 | 28 | public function toFloatExpression(): FloatExpression 29 | return this.toFloatLikeExpressionData(FloatExpression.constantFactor); 30 | 31 | public function toAngleExpression(): AngleExpression 32 | return this.toFloatLikeExpressionData(AngleExpression.constantFactor); 33 | 34 | /** 35 | Creates an `AssemblyCode` that assigns `this` value to the int register. 36 | **/ 37 | public function load(context: CompileContext): AssemblyCode { 38 | return switch this.data { 39 | case Constant(value): 40 | Load(Int(Imm(value.toImmediateValue()))); 41 | case Runtime(expression): 42 | expression.load(context); 43 | } 44 | } 45 | 46 | /** 47 | Creates an `AssemblyCode` that runs `instruction` 48 | receiving `this` value as argument via register. 49 | **/ 50 | public function use(context: CompileContext, instruction: Instruction): AssemblyCode 51 | return [load(context), [instruction]].flatten(); 52 | 53 | public function tryGetConstant(): Maybe { 54 | return switch this.data { 55 | case Constant(value): 56 | Maybe.from(value.toImmediateValue()); 57 | case Runtime(expression): 58 | Maybe.none(); 59 | } 60 | } 61 | 62 | public function unaryOperation(instruction: Instruction): IntLikeExpressionData { 63 | return create( 64 | Runtime( 65 | IntLikeRuntimeExpressionEnum.UnaryOperation( 66 | instruction, 67 | this 68 | ) 69 | ) 70 | ); 71 | } 72 | 73 | public function binaryOperation( 74 | instruction: Instruction, 75 | other: IntLikeExpressionData 76 | ): IntLikeExpressionData { 77 | return create( 78 | Runtime( 79 | IntLikeRuntimeExpressionEnum.BinaryOperation( 80 | instruction, 81 | this, 82 | other 83 | ) 84 | ) 85 | ); 86 | } 87 | 88 | public function unaryMinus(): IntLikeExpressionData 89 | return unaryOperation(Minus(Int(Reg))); 90 | 91 | public function add(other: IntLikeExpressionData): IntLikeExpressionData 92 | return binaryOperation(Add(Int(RegBuf, Reg)), other); 93 | 94 | public function subtract(other: IntLikeExpressionData): IntLikeExpressionData 95 | return binaryOperation(Sub(Int(RegBuf, Reg)), other); 96 | 97 | public function multiply(other: IntLikeExpressionData): IntLikeExpressionData 98 | return binaryOperation(Mult(Int(RegBuf), Int(Reg)), other); 99 | 100 | public function divide(other: IntLikeExpressionData): IntLikeExpressionData 101 | return binaryOperation(Div(Int(RegBuf), Int(Reg)), other); 102 | 103 | public function modulo(other: IntLikeExpressionData): IntLikeExpressionData 104 | return binaryOperation(Mod(Int(RegBuf), Int(Reg)), other); 105 | 106 | public extern inline function toEnum() 107 | return this.data; 108 | 109 | public function toString(): String { 110 | return switch data { 111 | case Constant(value): 'IntC(${value.toString()})'; 112 | case Runtime(expression): 'IntR(${expression.toString()})'; 113 | } 114 | } 115 | 116 | function toFloatLikeExpressionData(constantFactor: Float): FloatLikeExpressionData { 117 | final loadAsFloat: (context: CompileContext) -> AssemblyCode = context -> [ 118 | this.load(context), 119 | [Cast(IntToFloat)], 120 | if (constantFactor == 1.0) [] else { 121 | [Mult(Float(Reg), Float(Imm(constantFactor)))]; 122 | } 123 | ].flatten(); 124 | 125 | final data = FloatLikeExpressionEnum.Runtime(FloatLikeRuntimeExpressionEnum.Custom(loadAsFloat)); 126 | 127 | return FloatLikeExpressionData.create(data); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/firedancer/script/expression/IntLocalVariableExpression.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.expression; 2 | 3 | import firedancer.script.nodes.DeclareLocalVariable; 4 | import firedancer.script.nodes.OperateLocalVariable; 5 | 6 | @:access(firedancer.script.expression.IntExpression) 7 | abstract IntLocalVariableExpression(String) { 8 | public extern inline function new(name: String) 9 | this = name; 10 | 11 | @:to public function toString(): String 12 | return 'IntRar($this)'; 13 | 14 | @:to function toIntExpression(): IntExpression { 15 | return IntExpression.fromEnum(IntLikeExpressionEnum.Runtime(Custom(context -> { 16 | final variable = context.localVariables.get(this); 17 | final code = variable.load(); 18 | switch variable.type { 19 | case Int: 20 | case Float: throw "Cannot cast float to int."; 21 | case Vec: throw "Cannot cast vector to int."; 22 | } 23 | code; 24 | }))); 25 | } 26 | 27 | @:to extern inline function toFloatExpression(): FloatExpression 28 | return get().toFloatExpression(); 29 | 30 | @:to extern inline function toAngleExpression(): AngleExpression 31 | return get().toAngleExpression(); 32 | 33 | @:op(-A) 34 | extern inline function unaryMinus(): IntExpression 35 | return get().unaryMinus(); 36 | 37 | @:op(A + B) @:commutative 38 | static extern inline function addOp( 39 | a: IntLocalVariableExpression, 40 | b: IntExpression 41 | ): IntExpression { 42 | return a.get().add(b); 43 | } 44 | 45 | @:op(A - B) 46 | static extern inline function subtract( 47 | a: IntLocalVariableExpression, 48 | b: IntExpression 49 | ): IntExpression { 50 | return a.get().subtract(b); 51 | } 52 | 53 | @:op(A - B) 54 | static extern inline function subtractR( 55 | a: IntExpression, 56 | b: IntLocalVariableExpression 57 | ): IntExpression { 58 | return a.subtract(b); 59 | } 60 | 61 | @:op(A * B) @:commutative 62 | static extern inline function multiply( 63 | a: IntLocalVariableExpression, 64 | b: IntExpression 65 | ): IntExpression { 66 | return a.get().multiply(b); 67 | } 68 | 69 | @:op(A / B) 70 | static extern inline function divide( 71 | a: IntLocalVariableExpression, 72 | b: IntExpression 73 | ): IntExpression { 74 | return a.get().divide(b); 75 | } 76 | 77 | @:op(A / B) 78 | static extern inline function divideR( 79 | a: IntExpression, 80 | b: IntLocalVariableExpression 81 | ): IntExpression { 82 | return a.divide(b); 83 | } 84 | 85 | @:op(A % B) 86 | static extern inline function modulo( 87 | a: IntLocalVariableExpression, 88 | b: IntExpression 89 | ): IntExpression { 90 | return a.get().modulo(b); 91 | } 92 | 93 | @:op(A % B) 94 | static extern inline function moduloR( 95 | a: IntExpression, 96 | b: IntLocalVariableExpression 97 | ): IntExpression { 98 | return a.modulo(b); 99 | } 100 | 101 | /** 102 | Declares `this` local variable so that it can be used in the current scope. 103 | **/ 104 | public function let(?initialValue: IntExpression): DeclareLocalVariable { 105 | return DeclareLocalVariable.fromInt(this, initialValue); 106 | } 107 | 108 | /** 109 | Assigns `value` to `this` local variable. 110 | **/ 111 | public function set(value: IntExpression): OperateLocalVariable 112 | return { name: this, operation: Set, value: value }; 113 | 114 | /** 115 | Adds `value` to `this` local variable. 116 | **/ 117 | public function add(value: IntExpression): OperateLocalVariable { 118 | final constant = value.tryGetConstant(); 119 | 120 | if (constant.isSome()) { 121 | switch constant.unwrap() { 122 | case 1: return increment(); 123 | case -1: return decrement(); 124 | default: 125 | } 126 | } 127 | 128 | return { name: this, operation: Add, value: value }; 129 | } 130 | 131 | public function increment(): OperateLocalVariable 132 | return { name: this, operation: Increment }; 133 | 134 | public function decrement(): OperateLocalVariable 135 | return { name: this, operation: Decrement }; 136 | 137 | function get() 138 | return toIntExpression(); 139 | } 140 | -------------------------------------------------------------------------------- /src/firedancer/script/expression/README.md: -------------------------------------------------------------------------------- 1 | **Spaghetti code!!!!** 2 | -------------------------------------------------------------------------------- /src/firedancer/script/expression/Transformation.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.expression; 2 | 3 | /** 4 | 2D coordinates transformation matrix. 5 | 6 | ``` 7 | a c e 8 | b d f 9 | 0 0 1 10 | ``` 11 | **/ 12 | @:structInit 13 | class Transformation { 14 | /** 15 | Multiplies two transformation matrices. 16 | **/ 17 | public static function multiply( 18 | mat1: Transformation, 19 | mat2: Transformation 20 | ): Transformation { 21 | return { 22 | a: mat1.a * mat2.a + mat1.c * mat2.b, 23 | b: mat1.b * mat2.a + mat1.d * mat2.b, 24 | c: mat1.a * mat2.c + mat1.c * mat2.d, 25 | d: mat1.b * mat2.c + mat1.d * mat2.d, 26 | e: mat1.a * mat2.e + mat1.c * mat2.f + mat1.e, 27 | f: mat1.b * mat2.e + mat1.d * mat2.f + mat1.f 28 | } 29 | } 30 | 31 | /** 32 | Applies a transformation matrix to a 2D vector. 33 | **/ 34 | public static function apply( 35 | mat: Transformation, 36 | vec: { x: FloatExpression, y: FloatExpression } 37 | ) { 38 | final x = vec.x; 39 | final y = vec.y; 40 | final newX = mat.a * x + mat.c * y + mat.e; 41 | final newY = mat.b * x + mat.d * y + mat.f; 42 | return { x: newX, y: newY }; 43 | } 44 | 45 | /** 46 | Creates a translation matrix. 47 | **/ 48 | public static function createTranslate( 49 | x: FloatExpression, 50 | y: FloatExpression 51 | ): Transformation { 52 | return { 53 | a: 1.0, 54 | b: 0.0, 55 | c: 0.0, 56 | d: 1.0, 57 | e: x, 58 | f: y 59 | } 60 | } 61 | 62 | /** 63 | Creates a rotation matrix. 64 | **/ 65 | public static function createRotate(angle: AngleExpression): Transformation { 66 | return { 67 | a: angle.cos(), 68 | b: angle.sin(), 69 | c: angle.sin().unaryMinus(), 70 | d: angle.cos(), 71 | e: 0.0, 72 | f: 0.0 73 | } 74 | } 75 | 76 | /** 77 | Creates a scaling matrix. 78 | @param y If not provided, this will be the same as `x`. 79 | **/ 80 | public static function createScale( 81 | x: FloatExpression, 82 | ?y: FloatExpression 83 | ): Transformation { 84 | if (y == null) y = x; 85 | 86 | return { 87 | a: x, 88 | b: 0.0, 89 | c: 0.0, 90 | d: y, 91 | e: 0.0, 92 | f: 0.0 93 | } 94 | } 95 | 96 | public final a: FloatExpression; 97 | public final b: FloatExpression; 98 | public final c: FloatExpression; 99 | public final d: FloatExpression; 100 | public final e: FloatExpression; 101 | public final f: FloatExpression; 102 | 103 | /** 104 | Adds a translation after `this` transformation and returns a composite matrix. 105 | **/ 106 | public function translate(x: FloatExpression, y: FloatExpression): Transformation 107 | return multiply(this, createTranslate(x, y)); 108 | 109 | /** 110 | Adds a rotation after `this` transformation and returns a composite matrix. 111 | **/ 112 | public function rotate(angle: AngleExpression): Transformation 113 | return multiply(this, createRotate(angle)); 114 | 115 | /** 116 | Adds a scaling after `this` transformation and returns a composite matrix. 117 | **/ 118 | public function scale(x: FloatExpression, ?y: FloatExpression): Transformation 119 | return multiply(this, createScale(x, y)); 120 | } 121 | -------------------------------------------------------------------------------- /src/firedancer/script/expression/VecExpression.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.expression; 2 | 3 | import firedancer.types.Azimuth; 4 | import firedancer.script.expression.VecExpressionData; 5 | 6 | /** 7 | Expression representing any 2D vector. 8 | **/ 9 | @:notNull @:forward 10 | abstract VecExpression(VecExpressionData) from VecExpressionData to VecExpressionData { 11 | @:from static function fromCartesianConstants( 12 | args: { x: Float, y: Float } 13 | ): VecExpression { 14 | final data: VecExpressionData = new CartesianVecExpressionData( 15 | args.x, 16 | args.y 17 | ); 18 | return data; 19 | } 20 | 21 | @:from static function fromCartesianExpressions( 22 | args: { x: FloatExpression, y: FloatExpression } 23 | ): VecExpression { 24 | final data: VecExpressionData = new CartesianVecExpressionData( 25 | args.x, 26 | args.y 27 | ); 28 | return data; 29 | } 30 | 31 | @:from static function fromPolarConstants( 32 | args: { length: Float, angle: Azimuth } 33 | ): VecExpression { 34 | final data: VecExpressionData = new PolarVecExpressionData( 35 | args.length, 36 | args.angle.toAngle() 37 | ); 38 | return data; 39 | } 40 | 41 | @:from static function fromPolarExpressionss( 42 | args: { length: FloatExpression, angle: AngleExpression } 43 | ): VecExpression { 44 | final data: VecExpressionData = new PolarVecExpressionData( 45 | args.length, 46 | args.angle 47 | ); 48 | return data; 49 | } 50 | 51 | @:to public function toString(): String 52 | return this.toString(); 53 | 54 | @:op(A / B) extern inline function divide(divisor: FloatExpression): VecExpression 55 | return this.divide(divisor); 56 | 57 | @:op(A / B) extern inline function divideByFloat(divisor: Float): VecExpression 58 | return this.divideByFloat(divisor); 59 | 60 | @:op(A / B) extern inline function dividebyInt(divisor: Int): VecExpression 61 | return divideByFloat(divisor); 62 | } 63 | -------------------------------------------------------------------------------- /src/firedancer/script/expression/VecExpressionData.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.expression; 2 | 3 | import sneaker.exception.NotOverriddenException; 4 | import firedancer.types.Azimuth; 5 | import firedancer.assembly.AssemblyCode; 6 | import firedancer.assembly.Instruction; 7 | 8 | /** 9 | Underlying type of `VecExpression`. 10 | **/ 11 | class VecExpressionData implements ExpressionData { 12 | final divisor: Maybe = Maybe.none(); 13 | 14 | public function new(?divisor: FloatExpression) { 15 | this.divisor = Maybe.from(divisor); 16 | } 17 | 18 | public function tryGetConstant(): Maybe<{x: Float, y: Float }> 19 | throw new NotOverriddenException(); 20 | 21 | public function divide(divisor: FloatExpression): VecExpressionData 22 | throw new NotOverriddenException(); 23 | 24 | public function transform(matrix: Transformation): VecExpressionData 25 | throw new NotOverriddenException(); 26 | 27 | public function divideByFloat(divisor: Float): VecExpressionData 28 | throw new NotOverriddenException(); 29 | 30 | public function load(context: CompileContext): AssemblyCode 31 | throw new NotOverriddenException(); 32 | 33 | /** 34 | Creates an `AssemblyCode` that runs `instruction` 35 | receiving `this` value as argument via register. 36 | **/ 37 | public function use(context: CompileContext, instruction: Instruction): AssemblyCode { 38 | final code = load(context); 39 | code.push(instruction); 40 | return code; 41 | } 42 | 43 | public function toString(): String 44 | throw new NotOverriddenException(); 45 | } 46 | 47 | @:structInit 48 | class CartesianVecExpressionData extends VecExpressionData { 49 | public final x: FloatExpression; 50 | public final y: FloatExpression; 51 | 52 | final transformation: Maybe = Maybe.none(); 53 | 54 | public function new( 55 | x: FloatExpression, 56 | y: FloatExpression, 57 | ?divisor: FloatExpression, 58 | ?transformation: Transformation 59 | ) { 60 | super(divisor); 61 | this.x = x; 62 | this.y = y; 63 | this.transformation = Maybe.from(transformation); 64 | } 65 | 66 | override public function tryGetConstant(): Maybe<{x: Float, y: Float }> { 67 | var x = this.x; 68 | var y = this.y; 69 | 70 | if (this.transformation.isSome()) { 71 | final newVec = Transformation.apply( 72 | this.transformation.unwrap(), 73 | { x: x, y: y } 74 | ); 75 | x = newVec.x; 76 | y = newVec.y; 77 | } 78 | 79 | final xConstant = x.tryGetConstant(); 80 | if (xConstant.isNone()) return Maybe.none(); 81 | 82 | final yConstant = y.tryGetConstant(); 83 | if (yConstant.isNone()) return Maybe.none(); 84 | 85 | final xVal = xConstant.unwrap(); 86 | final yVal = yConstant.unwrap(); 87 | 88 | if (divisor.isNone()) 89 | return Maybe.from({ x: xVal, y: yVal }); 90 | else { 91 | final divisorConstant = divisor.unwrap().tryGetConstant(); 92 | if (divisorConstant.isNone()) return Maybe.none(); 93 | final divVal = divisorConstant.unwrap(); 94 | return Maybe.from({ x: xVal / divVal, y: yVal / divVal }); 95 | } 96 | } 97 | 98 | override public function divide(divisor: FloatExpression): CartesianVecExpressionData { 99 | if (this.divisor.isSome()) divisor = this.divisor.unwrap() * divisor; 100 | return new CartesianVecExpressionData( 101 | x, 102 | y, 103 | divisor, 104 | transformation.nullable() 105 | ); 106 | } 107 | 108 | override public function transform( 109 | matrix: Transformation 110 | ): CartesianVecExpressionData { 111 | return new CartesianVecExpressionData( 112 | x, 113 | y, 114 | divisor.nullable(), 115 | if (this.transformation.isSome()) Transformation.multiply( 116 | this.transformation.unwrap(), 117 | matrix 118 | ) else matrix 119 | ); 120 | } 121 | 122 | override public function divideByFloat(divisor: Float): CartesianVecExpressionData 123 | return divide(divisor); 124 | 125 | override public function load(context: CompileContext): AssemblyCode { 126 | var x = this.x; 127 | var y = this.y; 128 | 129 | if (this.transformation.isSome()) { 130 | final newVec = Transformation.apply( 131 | this.transformation.unwrap(), 132 | { x: x, y: y } 133 | ); 134 | x = newVec.x; 135 | y = newVec.y; 136 | } 137 | 138 | final divisor = this.divisor; 139 | 140 | final loadVecWithoutDivisor = [ 141 | y.load(context), 142 | [Push(Float(Reg))], 143 | x.load(context), 144 | [ 145 | Save(Float(Reg)), 146 | Pop(Float), 147 | Cast(CartesianToVec) 148 | ] 149 | ].flatten(); 150 | 151 | #if js 152 | loadVecWithoutDivisor.length; // Workaround for terser --mangle bug 153 | #end 154 | 155 | if (divisor.isNone()) { 156 | // rVec 157 | return loadVecWithoutDivisor; 158 | } else { 159 | // rVec / rDiv 160 | return [ 161 | loadVecWithoutDivisor, 162 | divisor.unwrap().load(context), 163 | [Div(Vec(Reg), Float(Reg))] 164 | ].flatten(); 165 | } 166 | } 167 | 168 | override public function toString(): String 169 | return '{ x: ${x.toString()}, y: ${y.toString()} }'; 170 | } 171 | 172 | @:structInit 173 | class PolarVecExpressionData extends VecExpressionData { 174 | public final length: FloatExpression; 175 | public final angle: AngleExpression; 176 | 177 | public function new( 178 | length: FloatExpression, 179 | angle: AngleExpression, 180 | ?divisor: FloatExpression 181 | ) { 182 | super(divisor); 183 | this.length = length; 184 | this.angle = angle; 185 | } 186 | 187 | override public function tryGetConstant(): Maybe<{x: Float, y: Float }> { 188 | final lengthConstant = length.tryGetConstant(); 189 | if (lengthConstant.isNone()) return Maybe.none(); 190 | 191 | final angleConstant = angle.tryGetConstant(); 192 | if (angleConstant.isNone()) return Maybe.none(); 193 | 194 | final lenVal = lengthConstant.unwrap(); 195 | final angVal = angleConstant.unwrap(); 196 | final vec = Azimuth.fromRadians(angVal).toVec2D(lenVal); 197 | 198 | if (divisor.isNone()) { 199 | return Maybe.from({ x: vec.x, y: vec.y }); 200 | } else { 201 | final divisorValue = divisor.unwrap().tryGetConstant(); 202 | if (divisorValue.isNone()) return Maybe.none(); 203 | final divVal = divisorValue.unwrap(); 204 | return Maybe.from({ x: vec.x / divVal, y: vec.y / divVal }); 205 | } 206 | } 207 | 208 | override public function divide(divisor: FloatExpression): PolarVecExpressionData { 209 | if (this.divisor.isSome()) divisor = this.divisor.unwrap() * divisor; 210 | return new PolarVecExpressionData( 211 | length, 212 | angle, 213 | divisor 214 | ); 215 | } 216 | 217 | override public function transform( 218 | matrix: Transformation 219 | ): CartesianVecExpressionData { 220 | // TODO: optimize 221 | return new CartesianVecExpressionData( 222 | length * angle.cos(), 223 | length * angle.sin(), 224 | divisor.nullable(), 225 | matrix 226 | ); 227 | } 228 | 229 | override public function divideByFloat(divisor: Float): PolarVecExpressionData 230 | return divide(divisor); 231 | 232 | override public function load(context: CompileContext): AssemblyCode { 233 | var length = this.length; 234 | final angle = this.angle; 235 | 236 | final loadVecWithoutDivisor = [ 237 | angle.load(context), 238 | [Push(Float(Reg))], 239 | length.load(context), 240 | [ 241 | Save(Float(Reg)), 242 | Pop(Float), 243 | Cast(PolarToVec) 244 | ] 245 | ].flatten(); 246 | 247 | if (divisor.isNone()) { 248 | // rVec 249 | return loadVecWithoutDivisor; 250 | } else { 251 | // rVec / rDiv 252 | return [ 253 | loadVecWithoutDivisor, 254 | divisor.unwrap().load(context), 255 | [Div(Vec(Reg), Float(Reg))] 256 | ].flatten(); 257 | } 258 | } 259 | 260 | override public function toString(): String 261 | return '{ r: ${length.toString()}, θ: ${angle.toString()} }'; 262 | } 263 | -------------------------------------------------------------------------------- /src/firedancer/script/expression/import.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.expression; 2 | 3 | import firedancer.script.CompileContext; 4 | -------------------------------------------------------------------------------- /src/firedancer/script/expression/subtypes/FloatLikeConstant.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.expression.subtypes; 2 | 3 | import firedancer.types.Angle; 4 | 5 | /** 6 | Value that represents a float-like constant. 7 | **/ 8 | @:notNull @:forward 9 | abstract FloatLikeConstant( 10 | FloatLikeConstantData 11 | ) from FloatLikeConstantData to FloatLikeConstantData { 12 | /** 13 | Casts `Float` to `FloatLikeConstant`. 14 | **/ 15 | @:from static extern inline function fromFloat(value: Float): FloatLikeConstant 16 | return FloatLikeConstantData.fromFloat(value); 17 | 18 | /** 19 | Casts `Angle` to `FloatLikeConstant`. 20 | **/ 21 | @:from static extern inline function fromAngle(value: Angle): FloatLikeConstant 22 | return FloatLikeConstantData.fromAngle(value); 23 | 24 | @:to public function toString(): String 25 | return this.toString(); 26 | 27 | @:op(-A) inline function unaryMinus(): FloatLikeConstant 28 | return this.unaryMinus(); 29 | 30 | @:op(A + B) static function add( 31 | a: FloatLikeConstant, 32 | b: FloatLikeConstant 33 | ): FloatLikeConstant { 34 | return a.add(b); 35 | } 36 | 37 | @:op(A - B) static function subtract( 38 | a: FloatLikeConstant, 39 | b: FloatLikeConstant 40 | ): FloatLikeConstant { 41 | return a.subtract(b); 42 | } 43 | 44 | @:op(A * B) static function multiply( 45 | a: FloatLikeConstant, 46 | b: FloatLikeConstant 47 | ): FloatLikeConstant { 48 | return a.multiply(b); 49 | } 50 | 51 | @:op(A / B) static function divide( 52 | a: FloatLikeConstant, 53 | b: FloatLikeConstant 54 | ): FloatLikeConstant { 55 | return a.divide(b); 56 | } 57 | 58 | @:op(A % B) static function modulo( 59 | a: FloatLikeConstant, 60 | b: FloatLikeConstant 61 | ): FloatLikeConstant { 62 | return a.modulo(b); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/firedancer/script/expression/subtypes/FloatLikeConstantData.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.expression.subtypes; 2 | 3 | import reckoner.Geometry.DEGREES_TO_RADIANS; 4 | import firedancer.vm.Geometry; 5 | import firedancer.types.Angle; 6 | 7 | /** 8 | The underlying type of `FloatLikeConstant`. 9 | **/ 10 | @:structInit 11 | class FloatLikeConstantData { 12 | public static function fromFloat(value: Float): FloatLikeConstantData 13 | return { value: value, factor: 1.0 }; 14 | 15 | public static function fromAngle(value: Angle): FloatLikeConstantData 16 | return { value: value.toDegrees(), factor: DEGREES_TO_RADIANS }; 17 | 18 | /** 19 | The non-scaled value. 20 | **/ 21 | public final value: Float; 22 | 23 | /** 24 | The scale factor for multiplying `this` (e.g. the degrees-to-radians factor). 25 | **/ 26 | public final factor: Float; 27 | 28 | /** 29 | @return The non-scaled value of `this`. 30 | **/ 31 | public inline function raw(): Float 32 | return this.value; 33 | 34 | public function unaryMinus(): FloatLikeConstantData { 35 | return { value: -value, factor: factor }; 36 | } 37 | 38 | public function sin(): FloatLikeConstantData { 39 | return { value: Geometry.sin(value * factor), factor: 1.0 }; 40 | } 41 | 42 | public function cos(): FloatLikeConstantData { 43 | return { value: Geometry.cos(value * factor), factor: 1.0 }; 44 | } 45 | 46 | public function add(other: FloatLikeConstantData): FloatLikeConstantData { 47 | if (this.factor != other.factor) 48 | throw "Cannot add constants with different factors."; 49 | 50 | return { value: this.value + other.value, factor: this.factor }; 51 | } 52 | 53 | public function subtract(other: FloatLikeConstantData): FloatLikeConstantData { 54 | if (this.factor != other.factor) 55 | throw "Cannot subtract constant from another with different factor."; 56 | 57 | return { value: this.value - other.value, factor: this.factor }; 58 | } 59 | 60 | public function multiply(other: FloatLikeConstantData): FloatLikeConstantData { 61 | if (this.factor != 1.0 && other.factor != 1.0) 62 | throw "Cannot multiply scaled constant by another scaled one."; 63 | 64 | return { value: this.value * other.value, factor: this.factor }; 65 | } 66 | 67 | public function divide(other: FloatLikeConstantData): FloatLikeConstantData { 68 | if (other.factor != 1.0) 69 | throw "Cannot divide constant by another scaled one."; 70 | 71 | return { value: this.value / other.value, factor: this.factor }; 72 | } 73 | 74 | public function modulo(other: FloatLikeConstantData): FloatLikeConstantData { 75 | if (other.factor != 1.0) 76 | throw "Cannot perform modulo operation with a scaled divisor."; 77 | 78 | return { value: this.value % other.value, factor: this.factor }; 79 | } 80 | 81 | /** 82 | Converts `this` to `Float` for writing into `AssemblyCode`. 83 | **/ 84 | public function toImmediateValue(): Float 85 | return factor * value; 86 | 87 | public function toString(): String 88 | return '{v:$value,f:$factor}'; 89 | } 90 | -------------------------------------------------------------------------------- /src/firedancer/script/expression/subtypes/FloatLikeRuntimeExpression.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.expression.subtypes; 2 | 3 | import firedancer.assembly.AssemblyCode; 4 | 5 | typedef FloatLikeRuntimeExpressionEnum = RuntimeExpressionEnum; 6 | 7 | /** 8 | Abstract over `FloatLikeRuntimeExpressionEnum`. 9 | **/ 10 | @:notNull @:forward 11 | abstract FloatLikeRuntimeExpression( 12 | FloatLikeRuntimeExpressionEnum 13 | ) from FloatLikeRuntimeExpressionEnum to FloatLikeRuntimeExpressionEnum { 14 | @:to public function toString(): String { 15 | return switch this { 16 | case Inst(loadV): 17 | 'Inst(${loadV.toString()})'; 18 | case UnaryOperation(_, operand): 19 | 'UnOp(${operand.toString()})'; 20 | case BinaryOperation(_, operandA, operandB): 21 | 'BiOp(${operandA.toString()}, ${operandB.toString()})'; 22 | case Custom(_): 23 | "Custom"; 24 | } 25 | } 26 | 27 | /** 28 | Creates an `AssemblyCode` that assigns `this` value to the float register. 29 | **/ 30 | public function load(context: CompileContext): AssemblyCode { 31 | return switch this { 32 | case Inst(loadV): 33 | loadV; 34 | 35 | case UnaryOperation(instruction, operandExpr): 36 | final code = operandExpr.load(context); 37 | code.push(instruction); 38 | code; 39 | 40 | case BinaryOperation(instruction, operandExprA, operandExprB): 41 | final code:AssemblyCode = []; 42 | code.pushFromArray(operandExprB.load(context)); 43 | code.push(Push(Float(Reg))); 44 | code.pushFromArray(operandExprA.load(context)); 45 | code.push(Save(Float(Reg))); 46 | code.push(Pop(Float)); 47 | code.push(instruction); 48 | code; 49 | 50 | case Custom(load): 51 | load(context); 52 | } 53 | } 54 | 55 | public extern inline function toEnum(): FloatLikeRuntimeExpressionEnum 56 | return this; 57 | } 58 | -------------------------------------------------------------------------------- /src/firedancer/script/expression/subtypes/IntLikeConstant.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.expression.subtypes; 2 | 3 | /** 4 | Value that represents an int-like constant. 5 | **/ 6 | @:notNull 7 | abstract IntLikeConstant(haxe.Int32) from Int from haxe.Int32 { 8 | public inline function raw(): haxe.Int32 9 | return this; 10 | 11 | @:to public function toString(): String 12 | return Std.string(this); 13 | 14 | @:op(-A) function unaryMinus(): IntLikeConstant; 15 | 16 | @:op(A + B) static function add( 17 | a: IntLikeConstant, 18 | b: IntLikeConstant 19 | ): IntLikeConstant; 20 | 21 | @:op(A - B) static function subtract( 22 | a: IntLikeConstant, 23 | b: IntLikeConstant 24 | ): IntLikeConstant; 25 | 26 | @:op(A * B) static function multiply( 27 | a: IntLikeConstant, 28 | b: IntLikeConstant 29 | ): IntLikeConstant; 30 | 31 | @:op(A / B) static function divide( 32 | a: IntLikeConstant, 33 | b: IntLikeConstant 34 | ): IntLikeConstant; 35 | 36 | /** 37 | Converts `this` to `Int` for writing into `AssemblyCode`. 38 | **/ 39 | public extern inline function toImmediateValue(): Int 40 | return this; 41 | } 42 | -------------------------------------------------------------------------------- /src/firedancer/script/expression/subtypes/IntLikeRuntimeExpression.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.expression.subtypes; 2 | 3 | import firedancer.assembly.AssemblyCode; 4 | 5 | typedef IntLikeRuntimeExpressionEnum = RuntimeExpressionEnum; 6 | 7 | /** 8 | Abstract over `IntLikeRuntimeExpressionEnum`. 9 | **/ 10 | @:notNull @:forward 11 | abstract IntLikeRuntimeExpression( 12 | IntLikeRuntimeExpressionEnum 13 | ) from IntLikeRuntimeExpressionEnum to IntLikeRuntimeExpressionEnum { 14 | @:to public function toString(): String { 15 | return switch this { 16 | case Inst(loadV): 17 | 'Inst(${loadV.toString()})'; 18 | case UnaryOperation(_, operand): 19 | 'UnOp(${operand.toString()})'; 20 | case BinaryOperation(_, operandA, operandB): 21 | 'BiOp(${operandA.toString()}, ${operandB.toString()})'; 22 | case Custom(_): 23 | "Custom"; 24 | } 25 | } 26 | 27 | /** 28 | Creates an `AssemblyCode` that assigns `this` value to the int register. 29 | **/ 30 | public function load(context: CompileContext): AssemblyCode { 31 | return switch this { 32 | case Inst(loadV): 33 | loadV; 34 | 35 | case UnaryOperation(instruction, operandExpr): 36 | final code = operandExpr.load(context); 37 | code.push(instruction); 38 | code; 39 | 40 | case BinaryOperation(instruction, operandExprA, operandExprB): 41 | final code:AssemblyCode = []; 42 | code.pushFromArray(operandExprB.load(context)); 43 | code.push(Push(Int(Reg))); 44 | code.pushFromArray(operandExprA.load(context)); 45 | code.push(Save(Int(Reg))); 46 | code.push(Pop(Int)); 47 | code.push(instruction); 48 | code; 49 | 50 | case Custom(load): 51 | load(context); 52 | } 53 | } 54 | 55 | public extern inline function toEnum(): IntLikeRuntimeExpressionEnum 56 | return this; 57 | } 58 | -------------------------------------------------------------------------------- /src/firedancer/script/expression/subtypes/RuntimeExpressionEnum.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.expression.subtypes; 2 | 3 | import firedancer.assembly.AssemblyCode; 4 | import firedancer.assembly.Instruction; 5 | 6 | /** 7 | Expression that has to be evaluated in runtime. 8 | **/ 9 | enum RuntimeExpressionEnum { 10 | /** 11 | @param loadV `Instruction` for loading the value to a register. 12 | **/ 13 | Inst(loadV: Instruction); 14 | 15 | /** 16 | @param operand An expression to be operated. 17 | **/ 18 | UnaryOperation(instruction: Instruction, operand: E); 19 | 20 | /** 21 | @param operandA The first expression to be operated. 22 | @param operandB The second expression to be operated. 23 | **/ 24 | BinaryOperation( 25 | instruction: Instruction, 26 | operandA: E, 27 | operandB: E 28 | ); 29 | 30 | Custom(load: (context: CompileContext) -> AssemblyCode); 31 | } 32 | -------------------------------------------------------------------------------- /src/firedancer/script/nodes/AddActorProperty.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.nodes; 2 | 3 | import firedancer.vm.Constants.IntSize; 4 | import firedancer.assembly.Instruction; 5 | import firedancer.assembly.AssemblyCode; 6 | import firedancer.assembly.types.ActorProperty.create as prop; 7 | import firedancer.assembly.types.ActorPropertyType; 8 | import firedancer.script.expression.*; 9 | 10 | /** 11 | Operates actor's property (e.g. position). 12 | **/ 13 | @:ripper_verified 14 | class AddActorProperty extends AstNode implements ripper.Data { 15 | final propType: ActorPropertyType; 16 | final operation: ActorPropertyAddOperation; 17 | 18 | /** 19 | Performs this operation gradually in `frames`. 20 | **/ 21 | public inline function frames(frames: IntExpression) 22 | return new AddActorPropertyLinear(propType, operation, frames); 23 | 24 | override inline function containsWait(): Bool 25 | return false; 26 | 27 | override function toAssembly(context: CompileContext): AssemblyCode { 28 | final c = context; 29 | return switch propType { 30 | case Position: 31 | switch operation { 32 | case AddVector(e): e.use(c, Increase(Vec(Reg), prop(Position, Vector))); 33 | case AddLength(e): e.use(c, Increase(Float(Reg), prop(Position, Length))); 34 | case AddAngle(e): e.use(c, Increase(Float(Reg), prop(Position, Angle))); 35 | } 36 | case Velocity: 37 | switch operation { 38 | case AddVector(e): e.use(c, Increase(Vec(Reg), prop(Velocity, Vector))); 39 | case AddLength(e): e.use(c, Increase(Float(Reg), prop(Velocity, Length))); 40 | case AddAngle(e): e.use(c, Increase(Float(Reg), prop(Velocity, Angle))); 41 | } 42 | case ShotPosition: 43 | switch operation { 44 | case AddVector(e): e.use(c, Increase(Vec(Reg), prop(ShotPosition, Vector))); 45 | case AddLength(e): e.use(c, Increase(Float(Reg), prop(ShotPosition, Length))); 46 | case AddAngle(e): e.use(c, Increase(Float(Reg), prop(ShotPosition, Angle))); 47 | } 48 | case ShotVelocity: 49 | switch operation { 50 | case AddVector(e): e.use(c, Increase(Vec(Reg), prop(ShotVelocity, Vector))); 51 | case AddLength(e): e.use(c, Increase(Float(Reg), prop(ShotVelocity, Length))); 52 | case AddAngle(e): e.use(c, Increase(Float(Reg), prop(ShotVelocity, Angle))); 53 | } 54 | } 55 | } 56 | } 57 | 58 | @:ripper_verified 59 | class AddActorPropertyLinear extends AstNode implements ripper.Data { 60 | final propType: ActorPropertyType; 61 | final operation: ActorPropertyAddOperation; 62 | final frames: IntExpression; 63 | 64 | override inline function containsWait(): Bool { 65 | final constFrames = this.frames.tryGetConstant(); 66 | return if (constFrames.isSome()) 0 < constFrames.unwrap() else true; 67 | } 68 | 69 | override function toAssembly(context: CompileContext): AssemblyCode { 70 | final frames = this.frames; 71 | 72 | inline function getDivChange(isVec: Bool): AssemblyCode { 73 | final divRRR: Instruction = Div(isVec ? Vec(Reg) : Float(RegBuf), Float(Reg)); 74 | final code: AssemblyCode = isVec ? [] : [Save(Float(Reg))]; 75 | final loadFramesAsFloat = (frames : FloatExpression).load(context); 76 | code.pushFromArray(loadFramesAsFloat); 77 | code.push(divRRR); 78 | return code; 79 | } 80 | 81 | var loadChange: AssemblyCode; // Load the total change (before the loop) 82 | var divChange: AssemblyCode; // Get change rate (before the loop) 83 | var pushChange: Instruction; // Push change rate (before the loop) 84 | var peekChange: Instruction; // Peek change rate (in the loop) 85 | var addFromVolatile: Instruction; // Apply change rate (in the loop) 86 | var dropChange: Instruction; // Drop change rate (after the loop) 87 | 88 | switch operation { 89 | case AddVector(vec): 90 | loadChange = vec.load(context); 91 | divChange = getDivChange(true); 92 | pushChange = Push(Vec(Reg)); 93 | peekChange = Peek(Vec, IntSize); // skip the loop counter 94 | addFromVolatile = Increase(Vec(Reg), prop(propType, Vector)); 95 | dropChange = Drop(Vec); 96 | 97 | case AddLength(length): 98 | loadChange = length.load(context); 99 | divChange = getDivChange(false); 100 | pushChange = Push(Float(Reg)); 101 | peekChange = Peek(Float, IntSize); // skip the loop counter 102 | addFromVolatile = Increase(Float(Reg), prop(propType, Length)); 103 | dropChange = Drop(Float); 104 | 105 | case AddAngle(angle): 106 | loadChange = angle.load(context); 107 | divChange = getDivChange(false); 108 | pushChange = Push(Float(Reg)); 109 | peekChange = Peek(Float, IntSize); // skip the loop counter 110 | addFromVolatile = Increase(Float(Reg), prop(propType, Angle)); 111 | dropChange = Drop(Float); 112 | } 113 | 114 | final prepare: AssemblyCode = loadChange.concat(divChange).concat([pushChange]); 115 | 116 | final body: AssemblyCode = [ 117 | Break, 118 | peekChange, 119 | addFromVolatile 120 | ]; 121 | 122 | // frames should be already loaded to int register in getDivChange() if it's not a constant 123 | final loopedBody = constructLoop(context, Push(Int(Reg)), body); 124 | 125 | final complete: AssemblyCode = [dropChange]; 126 | 127 | return [ 128 | prepare, 129 | loopedBody, 130 | complete 131 | ].flatten(); 132 | } 133 | } 134 | 135 | /** 136 | Represents an operation on actor's property. 137 | **/ 138 | @:using(firedancer.script.nodes.AddActorProperty.ActorPropertyAddOperationExtension) 139 | enum ActorPropertyAddOperation { 140 | AddVector(arg: VecExpression); 141 | // AddX(arg: FloatExpression); 142 | // AddY(arg: FloatExpression); 143 | AddLength(arg: FloatExpression); 144 | AddAngle(arg: AngleExpression); 145 | } 146 | 147 | class ActorPropertyAddOperationExtension { 148 | /** 149 | Divides the value to be added by `divisor`. 150 | **/ 151 | public static function divide( 152 | addOperation: ActorPropertyAddOperation, 153 | divisor: IntExpression 154 | ) { 155 | return switch addOperation { 156 | case AddVector(arg): AddVector(arg / divisor); 157 | case AddLength(arg): AddLength(arg / divisor); 158 | case AddAngle(arg): AddAngle(arg / divisor); 159 | default: throw "Unsupported operation."; 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/firedancer/script/nodes/Aim.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.nodes; 2 | 3 | import firedancer.script.expression.FloatExpression; 4 | 5 | /** 6 | Sets actor's shot direction to the bearing to the target position. 7 | **/ 8 | @:ripper_verified 9 | class Aim extends AstNode implements ripper.Data { 10 | var speed: Maybe = Maybe.none(); 11 | 12 | /** 13 | Sets shot speed. 14 | **/ 15 | public inline function shotSpeed(speed: FloatExpression): Aim { 16 | this.speed = Maybe.from(speed); 17 | return this; 18 | } 19 | 20 | override inline function containsWait(): Bool 21 | return false; 22 | 23 | override function toAssembly(context: CompileContext): AssemblyCode { 24 | final node = new SetActorProperty(ShotVelocity, if (speed.isSome()) { 25 | SetVector({ length: speed.unwrap(), angle: Api.shot.angleToTarget }); 26 | } else { 27 | SetAngle(Api.shot.angleToTarget); 28 | }); 29 | 30 | return node.toAssembly(context); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/firedancer/script/nodes/Async.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.nodes; 2 | 3 | /** 4 | Runs any pattern in another thread. 5 | **/ 6 | @:ripper_verified 7 | class Async extends AstNode implements ripper.Data { 8 | final pattern: Ast; 9 | 10 | override inline function containsWait(): Bool 11 | return false; 12 | 13 | override function toAssembly(context: CompileContext): AssemblyCode { 14 | final programId = context.setCode(pattern.toAssembly(context)); 15 | 16 | return [UseThread(programId, Null)]; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/firedancer/script/nodes/Comment.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.nodes; 2 | 3 | /** 4 | Inserts a comment to the assembly code. 5 | **/ 6 | @:ripper_verified 7 | class Comment extends AstNode implements ripper.Data { 8 | final text: String; 9 | 10 | override inline function containsWait(): Bool 11 | return false; 12 | 13 | override function toAssembly(context: CompileContext): AssemblyCode { 14 | return [Comment(text)]; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/firedancer/script/nodes/Debug.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.nodes; 2 | 3 | import firedancer.vm.DebugCode; 4 | 5 | /** 6 | Runs debug process specified by `debugCode`. 7 | **/ 8 | @:ripper_verified 9 | class Debug extends AstNode implements ripper.Data { 10 | final debugCode: DebugCode; 11 | 12 | override inline function containsWait(): Bool 13 | return false; 14 | 15 | override function toAssembly(context: CompileContext): AssemblyCode { 16 | return [Debug(debugCode)]; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/firedancer/script/nodes/DeclareLocalVariable.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.nodes; 2 | 3 | import firedancer.assembly.Instruction; 4 | import firedancer.script.expression.AngleExpression; 5 | import firedancer.script.expression.FloatExpression; 6 | import firedancer.script.expression.IntExpression; 7 | import firedancer.script.expression.GenericExpression; 8 | 9 | /** 10 | Declares a local variable. 11 | **/ 12 | class DeclareLocalVariable extends AstNode { 13 | public static function fromInt( 14 | name: String, 15 | ?initialValue: IntExpression 16 | ): DeclareLocalVariable { 17 | if (initialValue == null) { 18 | initialValue = 0; 19 | } 20 | return new DeclareLocalVariable(name, initialValue); 21 | } 22 | 23 | public static function fromFloat( 24 | name: String, 25 | ?initialValue: FloatExpression 26 | ): DeclareLocalVariable { 27 | if (initialValue == null) { 28 | initialValue = 0.0; 29 | } 30 | return new DeclareLocalVariable(name, initialValue); 31 | } 32 | 33 | public static function fromAngle( 34 | name: String, 35 | ?initialValue: AngleExpression 36 | ): DeclareLocalVariable { 37 | if (initialValue == null) { 38 | initialValue = 0.0; 39 | } 40 | return new DeclareLocalVariable(name, initialValue); 41 | } 42 | 43 | final name: String; 44 | final initialValue: GenericExpression; 45 | 46 | public function new(name: String, initialValue: GenericExpression) { 47 | this.name = name; 48 | this.initialValue = initialValue; 49 | } 50 | 51 | override inline function containsWait(): Bool 52 | return false; 53 | 54 | override function toAssembly(context: CompileContext): AssemblyCode { 55 | final name = this.name; 56 | 57 | var storeRL: Instruction; 58 | var letVar: Instruction; 59 | 60 | switch initialValue.toEnum() { 61 | case IntExpr(_): 62 | letVar = Let(name, Int); 63 | storeRL = Store(Int(Reg), name); 64 | context.localVariables.push(name, Int); 65 | case FloatExpr(_) | AngleExpr(_): 66 | letVar = Let(name, Float); 67 | storeRL = Store(Float(Reg), name); 68 | context.localVariables.push(name, Float); 69 | case VecExpr(_): 70 | throw "Local variable of vector type is not supported."; 71 | } 72 | 73 | return { 74 | [ 75 | [letVar], 76 | initialValue.load(context), 77 | [storeRL] 78 | ].flatten(); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/firedancer/script/nodes/Duplicate.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.nodes; 2 | 3 | import firedancer.script.expression.IntExpression; 4 | import firedancer.script.expression.FloatExpression; 5 | import firedancer.script.expression.AngleExpression; 6 | 7 | /** 8 | Duplicates the provided `AstNode` with some duplication parameters, 9 | and then resets all changes made during the duplication. 10 | **/ 11 | class Duplicate extends AstNode { 12 | final node: AstNode; 13 | final count: IntExpression; 14 | final params: DuplicateParameters; 15 | 16 | public function new(node: AstNode, count: IntExpression, params: DuplicateParameters) { 17 | this.node = node; 18 | this.count = count; 19 | this.params = params; 20 | } 21 | 22 | override inline function containsWait(): Bool { 23 | return this.node.containsWait() || this.params.intervalFrames != null; 24 | } 25 | 26 | override function toAssembly(context: CompileContext): AssemblyCode { 27 | final params = this.params; 28 | final preparation: Array = []; 29 | final interval: Array = []; 30 | final completion: Array = []; 31 | 32 | final varCount = Api.intVar("__loopCnt"); 33 | preparation.push(varCount.let(this.count - 1)); 34 | 35 | if (params.intervalFrames != null) { 36 | interval.push(Api.wait(params.intervalFrames)); 37 | } 38 | 39 | if (params.shotDistanceChange != null) { 40 | final shotDistanceChange = params.shotDistanceChange; 41 | final varInitialShotDistance = Api.floatVar("__sDstBuf"); 42 | final varShotDistanceChangeRate = Api.floatVar("__sDstChgRt"); 43 | preparation.push(varInitialShotDistance.let(Api.shot.distance)); 44 | preparation.push( 45 | varShotDistanceChangeRate.let(shotDistanceChange / varCount) 46 | ); 47 | interval.push(Api.shot.distance.add(varShotDistanceChangeRate)); 48 | completion.push(Api.shot.distance.set(varInitialShotDistance)); 49 | } 50 | 51 | if (params.shotBearingRange != null) { 52 | final range: AngleRange = params.shotBearingRange; 53 | final varInitialShotBearing = Api.angleVar("__sBrgBuf"); 54 | final varShotBearingChangeRate = Api.angleVar("__sBrgChgRt"); 55 | preparation.push(varInitialShotBearing.let(Api.shot.bearing)); 56 | preparation.push(Api.shot.bearing.add(range.start)); 57 | preparation.push( 58 | varShotBearingChangeRate.let((range.end - range.start) / varCount) 59 | ); 60 | interval.push(Api.shot.bearing.add(varShotBearingChangeRate)); 61 | completion.push(Api.shot.bearing.set(varInitialShotBearing)); 62 | } 63 | 64 | if (params.shotSpeedChange != null) { 65 | final shotSpeedChange = params.shotSpeedChange; 66 | final varInitialShotSpeed = Api.floatVar("__sSpdBuf"); 67 | final varShotSpeedChangeRate = Api.floatVar("__sSpdChgRt"); 68 | preparation.push(varInitialShotSpeed.let(Api.shot.speed)); 69 | preparation.push(varShotSpeedChangeRate.let(shotSpeedChange / varCount)); 70 | interval.push(Api.shot.speed.add(varShotSpeedChangeRate)); 71 | completion.push(Api.shot.speed.set(varInitialShotSpeed)); 72 | } 73 | 74 | if (params.shotDirectionRange != null) { 75 | final range: AngleRange = params.shotDirectionRange; 76 | final varInitialShotDirection = Api.angleVar("__sDirBuf"); 77 | final varShotDirectionChangeRate = Api.angleVar("__sDirChgRt"); 78 | preparation.push(varInitialShotDirection.let(Api.shot.direction)); 79 | preparation.push(Api.shot.direction.add(range.start)); 80 | preparation.push( 81 | varShotDirectionChangeRate.let((range.end - range.start) / varCount) 82 | ); 83 | interval.push(Api.shot.direction.add(varShotDirectionChangeRate)); 84 | completion.push(Api.shot.direction.set(varInitialShotDirection)); 85 | } 86 | 87 | final rep = Api.rep(varCount, [ 88 | [this.node], 89 | interval 90 | ].flatten()); 91 | 92 | final totalAst: Ast = preparation.concat([rep, this.node]).concat(completion); 93 | 94 | return totalAst.toAssembly(context); 95 | } 96 | } 97 | 98 | private typedef AngleRange = { start: AngleExpression, end: AngleExpression }; 99 | 100 | typedef DuplicateParameters = { 101 | /** 102 | Number of interval frames to wait. 103 | **/ 104 | final ?intervalFrames: IntExpression; 105 | 106 | /** 107 | The total change of the shot distance, relative from the current value. 108 | **/ 109 | final ?shotDistanceChange: FloatExpression; 110 | 111 | /** 112 | The `start`/`end` values of the shot bearing, relative from the current value. 113 | **/ 114 | final ?shotBearingRange: AngleRange; 115 | 116 | /** 117 | The total change of the shot speed, relative from the current value. 118 | **/ 119 | final ?shotSpeedChange: FloatExpression; 120 | 121 | /** 122 | The `start`/`end` values of the shot direction, relative from the current value. 123 | **/ 124 | final ?shotDirectionRange: AngleRange; 125 | }; 126 | -------------------------------------------------------------------------------- /src/firedancer/script/nodes/EachFrame.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.nodes; 2 | 3 | /** 4 | Injects any `Ast` in every frame within the current node list being compiled. 5 | **/ 6 | @:ripper_verified 7 | class EachFrame extends AstNode { 8 | public function new(astToBeInjected: Ast) 9 | this.nodeType = EachFrame(astToBeInjected); 10 | 11 | override inline function containsWait(): Bool 12 | return false; 13 | 14 | override inline function toAssembly(context: CompileContext): AssemblyCode 15 | return []; 16 | } 17 | -------------------------------------------------------------------------------- /src/firedancer/script/nodes/End.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.nodes; 2 | 3 | /** 4 | Ends running the bullet pattern with a specific end code 5 | so that the end code is returned from the VM. 6 | **/ 7 | @:ripper_verified 8 | class End extends AstNode implements ripper.Data { 9 | final endCode: Int; 10 | 11 | override inline function containsWait(): Bool 12 | return false; 13 | 14 | override function toAssembly(context: CompileContext): AssemblyCode 15 | return [End(endCode)]; 16 | } 17 | -------------------------------------------------------------------------------- /src/firedancer/script/nodes/Event.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.nodes; 2 | 3 | import firedancer.script.expression.IntExpression; 4 | import firedancer.assembly.Instruction; 5 | import firedancer.assembly.types.EventType; 6 | 7 | /** 8 | Invokes any global/local event. 9 | **/ 10 | @:ripper_verified 11 | class Event extends AstNode implements ripper.Data { 12 | final eventType: EventType; 13 | final eventCode: IntExpression; 14 | 15 | override inline function containsWait(): Bool 16 | return false; 17 | 18 | override function toAssembly(context: CompileContext): AssemblyCode { 19 | final instruction: Instruction = Event(eventType); 20 | 21 | final code = eventCode.load(context); 22 | code.push(instruction); 23 | return code; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/firedancer/script/nodes/Fire.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.nodes; 2 | 3 | import firedancer.vm.FireArgument; 4 | 5 | /** 6 | Emits a new actor. 7 | **/ 8 | @:ripper_verified 9 | class Fire extends AstNode implements ripper.Data { 10 | final pattern: Maybe; 11 | var bindPosition = false; 12 | var fireCode(default, null): Int = 0; 13 | 14 | /** 15 | Binds the position of the actor being fired to the position of the actor that fires it. 16 | **/ 17 | public inline function bind(): Fire { 18 | this.bindPosition = true; 19 | return this; 20 | } 21 | 22 | /** 23 | Specifies the fire code value (which is `0` at default) to any user-defined value. 24 | 25 | This does not affect the FiredancerVM directly, but you can use the value 26 | to branch the process in your own `Emitter` class 27 | (e.g. switch graphics of the actor to be emitted). 28 | **/ 29 | public inline function code(fireCode: Int): Fire { 30 | this.fireCode = fireCode; 31 | return this; 32 | } 33 | 34 | override inline function containsWait(): Bool 35 | return false; 36 | 37 | override function toAssembly(context: CompileContext): AssemblyCode { 38 | final fireArgument: Maybe = if (this.pattern.isNone()) { 39 | Maybe.none(); 40 | } else { 41 | final nextLabelIdStack = context.nextLabelIdStack; 42 | nextLabelIdStack.push(UInt.zero); 43 | final programId = context.setCode(this.pattern.unwrap().toAssembly(context)); 44 | nextLabelIdStack.pop(); 45 | // TODO: check duplicates 46 | 47 | Maybe.from(FireArgument.from(programId, this.bindPosition)); 48 | }; 49 | 50 | return [fire(fireArgument, this.fireCode)]; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/firedancer/script/nodes/List.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.nodes; 2 | 3 | /** 4 | List of `AstNode` to be run sequentially. 5 | **/ 6 | @:ripper_verified 7 | class List extends AstNode implements ripper.Data { 8 | final nodes: Array; 9 | 10 | override function containsWait(): Bool { 11 | final nodes = this.nodes; 12 | var found = false; 13 | for (i in 0...nodes.length) if (nodes[i].containsWait()) { 14 | found = true; 15 | break; 16 | } 17 | return found; 18 | } 19 | 20 | override function toAssembly(context: CompileContext): AssemblyCode { 21 | final codeList: Array = []; 22 | final nodes = this.nodes; 23 | var everyFrameNodeCount = UInt.zero; 24 | 25 | context.localVariables.startScope(); 26 | 27 | for (i in 0...nodes.length) { 28 | final node = nodes[i]; 29 | 30 | switch node.nodeType { 31 | case EachFrame(astToBeInjected): 32 | context.pushInjectionCode(astToBeInjected.toAssembly(context)); 33 | ++everyFrameNodeCount; 34 | default: 35 | } 36 | 37 | codeList.push(node.toAssembly(context)); 38 | } 39 | 40 | final completion = context.localVariables.endScope(); 41 | codeList.push(completion); 42 | 43 | for (_ in 0...everyFrameNodeCount) context.popInjectionCode(); 44 | 45 | return codeList.flatten(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/firedancer/script/nodes/Loop.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.nodes; 2 | 3 | /** 4 | Loops the provided `AstNode` endlessly. 5 | **/ 6 | @:ripper_verified 7 | class Loop extends AstNode implements ripper.Data { 8 | final node: AstNode; 9 | 10 | override inline function containsWait(): Bool { 11 | return this.node.containsWait(); 12 | } 13 | 14 | override function toAssembly(context: CompileContext): AssemblyCode { 15 | if (!this.containsWait()) throw "Infinite loop must contain Wait."; 16 | 17 | final nextLabelIdStack = context.nextLabelIdStack; 18 | var nextLabelId = nextLabelIdStack.pop().unwrap(); 19 | final labelId = nextLabelId++; 20 | nextLabelIdStack.push(nextLabelId); 21 | 22 | final code: AssemblyCode = []; 23 | code.push(Label(labelId)); 24 | code.pushFromArray(this.node.toAssembly(context)); 25 | code.push(GotoLabel(labelId)); 26 | 27 | return code; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/firedancer/script/nodes/OperateLocalVariable.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.nodes; 2 | 3 | import firedancer.script.expression.GenericExpression; 4 | 5 | @:structInit 6 | class OperateLocalVariable extends AstNode { 7 | final name: String; 8 | final operation: LocalVariableOperation; 9 | final value: Maybe; 10 | 11 | public function new( 12 | name: String, 13 | operation: LocalVariableOperation, 14 | ?value: GenericExpression 15 | ) { 16 | switch operation { 17 | case Set | Add: if (value == null) throw "Missing value to set/add."; 18 | default: 19 | } 20 | 21 | this.name = name; 22 | this.operation = operation; 23 | this.value = Maybe.from(value); 24 | } 25 | 26 | override function containsWait(): Bool 27 | return false; 28 | 29 | override function toAssembly(context: CompileContext): AssemblyCode { 30 | final variable = context.localVariables.get(this.name); 31 | 32 | return switch this.operation { 33 | case Set: variable.setValue(this.value.unwrap()); 34 | case Add: variable.addValue(this.value.unwrap()); 35 | case Increment: variable.increment(); 36 | case Decrement: variable.decrement(); 37 | } 38 | } 39 | } 40 | 41 | private enum abstract LocalVariableOperation(Int) { 42 | final Set; 43 | final Add; 44 | final Increment; 45 | final Decrement; 46 | } 47 | -------------------------------------------------------------------------------- /src/firedancer/script/nodes/Parallel.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.nodes; 2 | 3 | /** 4 | Runs the first pattern in the current thread and each subsequent one in a separate thread. 5 | Then waits until all patterns are completed. 6 | **/ 7 | @:ripper_verified 8 | class Parallel extends AstNode implements ripper.Data { 9 | final array: Array; 10 | 11 | override inline function containsWait(): Bool 12 | return false; 13 | 14 | override function toAssembly(context: CompileContext): AssemblyCode { 15 | var nodes = this.array.copy(); 16 | final mainNode = nodes.shift(); 17 | if (mainNode.isNone()) return []; 18 | 19 | final main = mainNode.unwrap().toAssembly(context); 20 | final invokeSub: AssemblyCode = nodes.map(node -> { 21 | final programId = context.setCode(node.toAssembly(context)); 22 | return UseThread(programId, Int(Stack)); 23 | }); 24 | final awaitSub: AssemblyCode = nodes.map(_ -> AwaitThread); 25 | 26 | return [ 27 | invokeSub, 28 | main, 29 | awaitSub 30 | ].flatten(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/firedancer/script/nodes/Repeat.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.nodes; 2 | 3 | import firedancer.script.expression.IntExpression; 4 | 5 | /** 6 | Repeats the provided `AstNode`. 7 | **/ 8 | @:ripper_verified 9 | class Repeat extends AstNode implements ripper.Data { 10 | final node: AstNode; 11 | final repetitionCount: IntExpression; 12 | 13 | override inline function containsWait(): Bool { 14 | return this.node.containsWait(); 15 | } 16 | 17 | override function toAssembly(context: CompileContext): AssemblyCode { 18 | final count = this.repetitionCount; 19 | final body = this.node.toAssembly(context); 20 | final code = loop(context, body, count); 21 | 22 | return code; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/firedancer/script/nodes/SetActorProperty.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.nodes; 2 | 3 | import firedancer.vm.Constants.IntSize; 4 | import firedancer.assembly.Instruction; 5 | import firedancer.assembly.AssemblyCode; 6 | import firedancer.assembly.types.ActorProperty.create as prop; 7 | import firedancer.assembly.types.ActorPropertyType; 8 | import firedancer.script.expression.*; 9 | 10 | /** 11 | Sets actor's property (e.g. position). 12 | **/ 13 | @:ripper_verified 14 | class SetActorProperty extends AstNode implements ripper.Data { 15 | final propType: ActorPropertyType; 16 | final operation: ActorPropertySetOperation; 17 | 18 | /** 19 | Performs this operation gradually in `frames`. 20 | **/ 21 | public function frames(frames: IntExpression) 22 | return new SetActorPropertyLinear(propType, operation, frames); 23 | 24 | override inline function containsWait(): Bool 25 | return false; 26 | 27 | override function toAssembly(context: CompileContext): AssemblyCode { 28 | final c = context; 29 | 30 | return switch propType { 31 | case Position: 32 | switch operation { 33 | case SetVector(e, mat): 34 | if (mat != null) e = e.transform(mat); 35 | e.use(c, Set(Vec(Reg), prop(Position, Vector))); 36 | case SetLength(e): e.use(c, Set(Float(Reg), prop(Position, Length))); 37 | case SetAngle(e): e.use(c, Set(Float(Reg), prop(Position, Angle))); 38 | } 39 | case Velocity: 40 | switch operation { 41 | case SetVector(e, mat): 42 | if (mat != null) e = e.transform(mat); 43 | e.use(c, Set(Vec(Reg), prop(Velocity, Vector))); 44 | case SetLength(e): e.use(c, Set(Float(Reg), prop(Velocity, Length))); 45 | case SetAngle(e): e.use(c, Set(Float(Reg), prop(Velocity, Angle))); 46 | } 47 | case ShotPosition: 48 | switch operation { 49 | case SetVector(e, mat): 50 | if (mat != null) e = e.transform(mat); 51 | e.use(c, Set(Vec(Reg), prop(ShotPosition, Vector))); 52 | case SetLength(e): e.use(c, Set(Float(Reg), prop(ShotPosition, Length))); 53 | case SetAngle(e): e.use(c, Set(Float(Reg), prop(ShotPosition, Angle))); 54 | } 55 | case ShotVelocity: 56 | switch operation { 57 | case SetVector(e, mat): 58 | if (mat != null) e = e.transform(mat); 59 | e.use(c, Set(Vec(Reg), prop(ShotVelocity, Vector))); 60 | case SetLength(e): e.use(c, Set(Float(Reg), prop(ShotVelocity, Length))); 61 | case SetAngle(e): e.use(c, Set(Float(Reg), prop(ShotVelocity, Angle))); 62 | } 63 | } 64 | } 65 | } 66 | 67 | @:ripper_verified 68 | class SetActorVector extends SetActorProperty implements ripper.Data { 69 | public function new( 70 | propType: ActorPropertyType, 71 | vec: VecExpression, 72 | ?matrix: Transformation 73 | ) { 74 | super(propType, SetVector(vec, matrix)); 75 | } 76 | 77 | public function transform(matrix: Transformation): SetActorVector { 78 | return switch operation { 79 | case SetVector(vec, mat): 80 | new SetActorVector( 81 | propType, 82 | vec, 83 | if (mat != null) Transformation.multiply(mat, matrix) else matrix 84 | ); 85 | default: 86 | throw "Invalid operation in SetActorVector class."; 87 | } 88 | } 89 | 90 | public function translate(x: FloatExpression, y: FloatExpression): SetActorVector 91 | return this.transform(Transformation.createTranslate(x, y)); 92 | 93 | public function rotate(angle: AngleExpression): SetActorVector 94 | return this.transform(Transformation.createRotate(angle)); 95 | 96 | public function scale(x: FloatExpression, y: FloatExpression): SetActorVector 97 | return this.transform(Transformation.createScale(x, y)); 98 | } 99 | 100 | class SetActorPropertyLinear extends AstNode { 101 | final propType: ActorPropertyType; 102 | final operation: ActorPropertySetOperation; 103 | final frames: IntExpression; 104 | 105 | public function new( 106 | propType: ActorPropertyType, 107 | operation: ActorPropertySetOperation, 108 | frames: IntExpression 109 | ) { 110 | this.propType = propType; 111 | this.operation = operation; 112 | this.frames = frames; 113 | } 114 | 115 | override inline function containsWait(): Bool { 116 | final constFrames = this.frames.tryGetConstant(); 117 | return if (constFrames.isSome()) 0 < constFrames.unwrap() else true; 118 | } 119 | 120 | override function toAssembly(context: CompileContext): AssemblyCode { 121 | final frames = this.frames; 122 | 123 | inline function getDivChange(isVec: Bool): AssemblyCode { 124 | return { 125 | final divRRR: Instruction = Div(isVec ? Vec(Reg) : Float(RegBuf), Float(Reg)); 126 | final code: AssemblyCode = isVec ? [] : [Save(Float(Reg))]; 127 | final loadFramesAsFloat = (frames : FloatExpression).load(context); 128 | code.pushFromArray(loadFramesAsFloat); 129 | code.push(divRRR); 130 | code; 131 | }; 132 | } 133 | 134 | var getDiff: AssemblyCode; // Calculate total change (before the loop) 135 | var divChange: AssemblyCode; // Get change rate (before the loop) 136 | var pushChange: Instruction; // Push change rate (before the loop) 137 | var peekChange: Instruction; // Peek change rate (in the loop) 138 | var addFromVolatile: Instruction; // Apply change rate (in the loop) 139 | var dropChange: Instruction; // Drop change rate (after the loop) 140 | 141 | switch operation { 142 | case SetVector(vec, mat): 143 | if (mat != null) vec = vec.transform(mat); 144 | 145 | divChange = getDivChange(true); 146 | pushChange = Push(Vec(Reg)); 147 | peekChange = Peek(Vec, IntSize); // skip the loop counter 148 | dropChange = Drop(Vec); 149 | 150 | addFromVolatile = Increase(Vec(Reg), prop(propType, Vector)); 151 | 152 | final getDiffRR:Instruction = GetDiff(Vec(Reg), prop(propType, Vector)); 153 | getDiff = [vec.load(context), [getDiffRR]].flatten(); 154 | 155 | case SetLength(length): 156 | divChange = getDivChange(false); 157 | pushChange = Push(Float(Reg)); 158 | peekChange = Peek(Float, IntSize); // skip the loop counter 159 | dropChange = Drop(Float); 160 | 161 | addFromVolatile = Increase(Float(Reg), prop(propType, Length)); 162 | 163 | final getDiffRR:Instruction = GetDiff(Float(Reg), prop(propType, Length)); 164 | getDiff = length.load(context); 165 | getDiff.push(getDiffRR); 166 | 167 | case SetAngle(angle): 168 | divChange = getDivChange(false); 169 | pushChange = Push(Float(Reg)); 170 | peekChange = Peek(Float, IntSize); // skip the loop counter 171 | dropChange = Drop(Float); 172 | 173 | addFromVolatile = Increase(Float(Reg), prop(propType, Angle)); 174 | 175 | final getDiffRR:Instruction = GetDiff(Float(Reg), prop(propType, Angle)); 176 | getDiff = angle.load(context); 177 | getDiff.push(getDiffRR); 178 | } 179 | 180 | final prepare: AssemblyCode = getDiff.concat(divChange).concat([pushChange]); 181 | 182 | final body: AssemblyCode = [ 183 | Break, 184 | peekChange, 185 | addFromVolatile 186 | ]; 187 | // frames should be already loaded to int register in getDivChange() 188 | final loopedBody = constructLoop(context, Push(Int(Reg)), body); 189 | 190 | final complete: AssemblyCode = [dropChange]; 191 | 192 | return [ 193 | prepare, 194 | loopedBody, 195 | complete 196 | ].flatten(); 197 | } 198 | } 199 | 200 | @:ripper_verified 201 | class SetActorVectorLinear extends SetActorPropertyLinear implements ripper.Data { 202 | public function new( 203 | propType: ActorPropertyType, 204 | vec: VecExpression, 205 | frames: IntExpression, 206 | ?matrix: Transformation 207 | ) { 208 | super(propType, SetVector(vec, matrix), frames); 209 | } 210 | 211 | /** 212 | Applies transformation on the vector to be set. 213 | **/ 214 | public function transform(matrix: Transformation): SetActorVectorLinear { 215 | return switch operation { 216 | case SetVector(vec, mat): 217 | new SetActorVectorLinear( 218 | propType, 219 | vec, 220 | frames, 221 | if (mat != null) Transformation.multiply(mat, matrix) else matrix 222 | ); 223 | default: 224 | throw "Invalid operation in SetActorVectorLinear class."; 225 | } 226 | } 227 | 228 | public function translate(x: FloatExpression, y: FloatExpression): SetActorVectorLinear 229 | return this.transform(Transformation.createTranslate(x, y)); 230 | 231 | public function rotate(angle: AngleExpression): SetActorVectorLinear 232 | return this.transform(Transformation.createRotate(angle)); 233 | 234 | public function scale(x: FloatExpression, y: FloatExpression): SetActorVectorLinear 235 | return this.transform(Transformation.createScale(x, y)); 236 | } 237 | 238 | /** 239 | Represents a "set" operation on actor's property. 240 | **/ 241 | private enum ActorPropertySetOperation { 242 | SetVector(arg: VecExpression, ?mat: Transformation); 243 | // SetX(arg: FloatExpression); 244 | // SetY(arg: FloatExpression); 245 | SetLength(arg: FloatExpression); 246 | SetAngle(arg: AngleExpression); 247 | } 248 | -------------------------------------------------------------------------------- /src/firedancer/script/nodes/Wait.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.nodes; 2 | 3 | import firedancer.script.expression.IntExpression; 4 | 5 | /** 6 | Waits for a specific number of frames. 7 | **/ 8 | @:ripper_verified 9 | class Wait extends AstNode implements ripper.Data { 10 | /** 11 | Wait duration in frames. 12 | **/ 13 | final frames: IntExpression; 14 | 15 | override inline function containsWait(): Bool 16 | return true; 17 | 18 | override function toAssembly(context: CompileContext): AssemblyCode { 19 | final injectionCode = context.getInjectionCode(); 20 | final loopBody: AssemblyCode = injectionCode.concat([Break]); 21 | 22 | if (injectionCode.length == 0) 23 | return [ 24 | frames.load(context), 25 | [Push(Int(Reg)), CountDownBreak] 26 | ].flatten(); 27 | 28 | return loop(context, loopBody, frames); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/firedancer/script/nodes/import.hx: -------------------------------------------------------------------------------- 1 | package firedancer.script.nodes; 2 | 3 | import firedancer.assembly.AssemblyCode; 4 | import firedancer.assembly.Instruction; 5 | import firedancer.assembly.Builder.*; 6 | -------------------------------------------------------------------------------- /src/firedancer/types/Angle.hx: -------------------------------------------------------------------------------- 1 | package firedancer.types; 2 | 3 | import reckoner.ArcDegrees; 4 | 5 | /** 6 | Angle in degrees (clockwise, 360 for a full rotation). 7 | **/ 8 | @:notNull @:forward(toRadians) 9 | abstract Angle(ArcDegrees) from ArcDegrees from Float { 10 | /** 11 | Explicitly casts `Float` (in degrees) to `Angle`. 12 | **/ 13 | public static extern inline function fromDegrees(degrees: Float): Angle 14 | return ArcDegrees.from(degrees); 15 | 16 | /** 17 | Converts `Float` (in radians) to `Angle`. 18 | **/ 19 | public static extern inline function fromRadians(radians: Float): Angle 20 | return ArcDegrees.fromRadians(radians); 21 | 22 | @:op(-A) function unaryMinus(): Angle; 23 | 24 | @:op(A + B) 25 | static function add(a: Angle, b: Angle): Angle; 26 | 27 | @:op(A - B) 28 | static function subtract(a: Angle, b: Angle): Angle; 29 | 30 | @:op(A * B) @:commutative 31 | static function multiply(angle: Angle, factor: Float): Angle; 32 | 33 | @:op(A * B) @:commutative 34 | static function multiplyInt(angle: Angle, factor: Int): Angle; 35 | 36 | @:op(A / B) function divide(divisor: Float): Angle; 37 | 38 | @:op(A / B) function divideInt(divisor: Int): Angle; 39 | 40 | /** 41 | Casts `this` to `Float` in degrees. 42 | **/ 43 | public inline function toDegrees(): Float 44 | return this; 45 | } 46 | -------------------------------------------------------------------------------- /src/firedancer/types/Azimuth.hx: -------------------------------------------------------------------------------- 1 | package firedancer.types; 2 | 3 | import reckoner.TmpVec2D; 4 | import reckoner.Numeric.nearlyEqual; 5 | import firedancer.vm.Geometry; 6 | 7 | /** 8 | Azimuth value in degrees (north-based and clockwise, 360 for a full rotation). 9 | **/ 10 | @:notNull @:forward(toRadians, toDegrees) 11 | abstract Azimuth(Angle) { 12 | /** 13 | The north `Azimuth`. 14 | **/ 15 | public static extern inline final zero: Azimuth = cast 0.0; 16 | 17 | /** 18 | Creates an `Azimuth` value from degrees. 19 | **/ 20 | @:from public static extern inline function fromDegrees(degrees: Float): Azimuth { 21 | return new Azimuth(Angle.fromDegrees(degrees)); 22 | } 23 | 24 | /** 25 | Creates an `Azimuth` value from radians. 26 | **/ 27 | @:from public static extern inline function fromRadians(radians: Float): Azimuth { 28 | return new Azimuth(Angle.fromRadians(radians)); 29 | } 30 | 31 | @:op(A + B) @:commutative 32 | static extern inline function add(azimuth: Azimuth, displacement: Angle): Azimuth { 33 | return new Azimuth(azimuth.toAngle() + displacement); 34 | } 35 | 36 | @:op(A - B) 37 | static extern inline function subtract( 38 | azimuth: Azimuth, 39 | displacement: Angle 40 | ): Azimuth { 41 | return new Azimuth(azimuth.toAngle() - displacement); 42 | } 43 | 44 | public extern inline function toAngle(): Angle 45 | return this; 46 | 47 | /** 48 | Creates a 2D vector from a given `length` and `this` azimuth. 49 | 50 | Should not be used in runtime as this also does some error correction. 51 | **/ 52 | public extern inline function toVec2D(length: Float): TmpVec2D { 53 | final unitVec = Geometry.toUnitVec(this.toRadians()); 54 | 55 | var xFactor = unitVec.x; 56 | 57 | if (nearlyEqual(xFactor, 0.0)) xFactor = 0.0; 58 | if (nearlyEqual(xFactor, 1.0)) xFactor = 1.0; 59 | if (nearlyEqual(xFactor, -1.0)) xFactor = -1.0; 60 | if (nearlyEqual(xFactor, 0.5)) xFactor = 0.5; 61 | if (nearlyEqual(xFactor, -0.5)) xFactor = -0.5; 62 | 63 | var yFactor = unitVec.y; 64 | 65 | if (nearlyEqual(yFactor, 0.0)) yFactor = 0.0; 66 | if (nearlyEqual(yFactor, 1.0)) yFactor = 1.0; 67 | if (nearlyEqual(yFactor, -1.0)) yFactor = -1.0; 68 | if (nearlyEqual(yFactor, 0.5)) yFactor = 0.5; 69 | if (nearlyEqual(yFactor, -0.5)) yFactor = -0.5; 70 | 71 | return { x: xFactor * length, y: yFactor * length }; 72 | } 73 | 74 | extern inline function new(angle: Angle) 75 | this = angle; 76 | } 77 | --------------------------------------------------------------------------------