Compare commits

...

35 Commits

Author SHA1 Message Date
c665401fd8 use .call instead of native in Inventory peripheral 2025-05-23 15:39:44 +02:00
2799a0be3d added GeoScanner peripheral 2024-10-27 16:08:38 +01:00
e527dd5b6a added simple 3D navigation 2024-10-27 16:07:23 +01:00
fe17b4fd67 added inspect command to turtleCtl 2024-10-20 23:23:58 +02:00
90b4015ba1 get chunk from position 2024-10-20 22:20:33 +02:00
2dd85c2b26 added Tags class for list of classes 2024-10-20 22:18:23 +02:00
1d60e13792 idfk 2024-10-18 16:48:38 +02:00
9d6e8a366b fixed SinglePromise reset order 2024-10-18 16:37:46 +02:00
5167533c6d Ignore GPS messages from other dimension
refs #2
2024-10-17 00:14:39 +02:00
aac527ae89 added a timeout and improvements to GPS and INS
closes #6
2024-10-16 23:45:53 +02:00
af6a4c840b added SinglePromise and SingleTimeoutPromise 2024-10-16 23:42:21 +02:00
afbd1dfd68 improved Vector stuff 2024-10-16 23:39:07 +02:00
b305594ea4 improved networking 2024-10-15 01:19:36 +02:00
df7991763d added PaN 2024-10-14 21:42:52 +02:00
e0f8d274e7 renamed Pos and Pos3 2024-10-14 21:40:26 +02:00
08f41ccb0b constrained vec to only accept numbers 2024-10-14 12:11:14 +02:00
28ec48cc85 added PriorityQueue for later use 2024-10-12 20:58:10 +02:00
4f2b6e7c53 fixed uninitialized children array in RootElement 2024-08-17 01:11:38 +02:00
2e0bda7a6e renamed Perf to Peri 2024-08-17 00:58:01 +02:00
87c93f3ae0 TurtleCtl times parameter optional 2024-08-17 00:57:03 +02:00
628aef06e3 oops 2024-08-17 00:55:52 +02:00
2603527b67 pretty error reporting 2024-08-17 00:48:53 +02:00
39a7da716c improved logging system 2024-08-17 00:47:15 +02:00
afc0309222 switched from yarn to npm 2024-08-13 00:13:24 +02:00
0d9ce5d6b1 start terminal on boot when in debug 2024-08-13 00:11:37 +02:00
be58ed1c05 removed unused files 2024-08-13 00:10:47 +02:00
fe16799bbb added inventory peripheral 2024-08-12 15:16:32 +02:00
5f5e899f6e rewrote circle mine to use plans 2024-08-11 20:18:25 +02:00
8546659ae0 first implementation of turtle plans 2024-08-11 20:17:37 +02:00
249a48807a use digEmpty function in tunnel command 2024-08-05 17:09:23 +02:00
be405887e2 added digEmpty function 2024-08-05 17:09:02 +02:00
73e2ccdcb8 added turtle helper: combine 2024-08-05 17:01:41 +02:00
b05dae958d tunnel command to turtle ctl 2024-08-05 16:58:46 +02:00
3e993b84eb added turtle ctl dig command 2024-08-05 15:41:03 +02:00
09fef78f9a added doc for applications 2024-08-05 14:39:16 +02:00
64 changed files with 1746 additions and 653 deletions

View File

@@ -6,7 +6,7 @@ UNPACK_NAME = unpack.lua
UNPACK_POLYFILLED_NAME = unpack.polyfill.lua
UNPACK_MINIFYD_NAME = unpack.min.lua
BUILD_DIR = build
HAXE_FLAGS =
HAXE_FLAGS = -D message.reporting=pretty
POLYFILLED_NAME = bundle.polyfill.lua
POLYFILL_SRC = src/polyfill.lua
CREAFTOS_PATH = craftos
@@ -22,7 +22,7 @@ UNPACK_POLYFILLED_PATH := $(BUILD_DIR)/$(UNPACK_POLYFILLED_NAME)
all: clean build unpack
build: HAXE_FLAGS += -D analyzer-optimize --no-traces
build: HAXE_FLAGS += -D analyzer-optimize -D no-traces
build: $(MIN_PATH)
debug: HAXE_FLAGS += -D webconsole -D error_stack --debug
@@ -62,7 +62,7 @@ deps-hx:
.PHONY: deps-node
deps-node:
yarn install
npm install
.PHONY: clean
clean:

View File

@@ -111,10 +111,9 @@ A collection of `Pixel` is called a `Canvas` which is nothing more than a 2D arr
The concept of processes tryes to encapsulate programs. A process is basically an interface with the `run(handle: ProcessHandle)` method.
The idea is that you can register all you disposable resources in the handle and they will be disposed when the process is killed or crashes.
A process can be used as a command on the terminal or as a service. See [bin/HelloWorld.hx](../src/bin/HelloWorld.hx) for an example.
Basically everything that runs and is not part of the kernel is a process.
A process can be used as a command on the terminal or as a service. Basically everything that runs and is not part of the kernel is a process.
~~In order for you program to be used it needs to be registered in the `BinStore` and the `DCEHack` manually. ~~
More on that at [Applications](#applications).
# EndOfLoop
@@ -224,3 +223,31 @@ The follwing haxe flags can be used in the code or can be set in the makefile.
- `debug`: Is set when debug
- `kv_use_native`: If set use the native CC serialize and unserialize functions instead of the Haxe ones. See [KVStore.hx](../src/lib/KVStore.hx) for more.
# Applications
If you want to make an application you just need to create a class that implements IProcess and and has no arguments in the constructor.
In order to have the programm available from the terminal you need to add the `Binstore.includeBin` macro for the class. Here is an example:
```haxe
@:build(macros.Binstore.includeBin("HelloWorld", ["hello", "helloworld"]))
class HelloWorld implements IProcess {
public function new() {}
public function run(handle:ProcessHandle) {
handle.writeLine("Hello World");
handle.close(true);
}
}
```
Let's break this down:
- The `includeBin` macro is required to automaticly inlcude the app and to defeat the DCE optimisation. In early versions refections were used but DCE did not like that
- The first argument on `includeBin` is the name of the app. Only ever used in displaying.
- The second argument are the aliases. These are used to call the app from the terminal and other places
- The constructor can't have any arguments. It's also recommended to not do any major setup stuff in there.
- The `run` method is the entry point in the app.
- The handle is used to interact with the outside world. (theoretically at least)
- The `handle.close(true)` call is required to end a process. The app does not end when the run function is finished. The first arguments represents if the app was successfull.

35
package-lock.json generated Normal file
View File

@@ -0,0 +1,35 @@
{
"name": "haxe",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "haxe",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"luamin": "^1.0.4"
}
},
"node_modules/luamin": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/luamin/-/luamin-1.0.4.tgz",
"integrity": "sha1-lEUptY/G+k0x6s4uA1PUEhDw49M= sha512-z1h0bclRD/QGsS/Dz4Skp9z0qPTmmm+IKcrCapGmdTczPWVdN9f9jk6WqkNrcifQr8+n9Pzsm9WkwpOH1JBUAQ==",
"dependencies": {
"luaparse": "^0.2.1"
},
"bin": {
"luamin": "bin/luamin"
}
},
"node_modules/luaparse": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/luaparse/-/luaparse-0.2.1.tgz",
"integrity": "sha1-qo9WEysN6X0388mRqd9C4OF/ZWw= sha512-VKBcryd5nJte4ZNR29NOk8F/UkMipjeb4yoxcSS51z6QAzg9DXUC2WsfLniS0J1eh3pr/ZL3e9ha6V8fhoLbBQ==",
"bin": {
"luaparse": "bin/luaparse"
}
}
}
}

View File

@@ -6,6 +6,14 @@ import lib.HomeContext;
class Startup {
public static function main() {
#if debug
var term = BinStore.instantiate("terminal");
var pid = ProcessManager.run(term, {
// args: ["debug"]
});
var ctx = WindowManager.getContextByPID(pid);
WindowManager.focusContextToOutput(ctx[0], "main");
#else
if (MainTerm.instance.isColor()) {
var main = new HomeContext();
@@ -16,5 +24,6 @@ class Startup {
var ctx = WindowManager.getContextByPID(pid);
WindowManager.focusContextToOutput(ctx[0], "main");
}
#end
}
}

View File

@@ -2,7 +2,7 @@ package bin;
import lib.CLIAppBase;
import kernel.gps.INS;
import lib.Pos3;
import lib.WorldPos;
import lib.Vec.Vec3;
using tink.CoreApi;
@@ -15,7 +15,7 @@ class GPS extends CLIAppBase {
var y:Float = args.getFloat("y");
var z:Float = args.getFloat("z");
var pos:Pos3 = new Vec3<Float>(x, y, z);
var pos:WorldPos = new Vec3<Float>(x, y, z);
kernel.gps.GPS.setManualPosition(pos);
@@ -51,11 +51,12 @@ class GPS extends CLIAppBase {
});
registerAsyncSubcommand("locate", (args) -> {
return kernel.gps.GPS.locate().map((pos) -> {
if (pos != null) {
return kernel.gps.GPS.locate().map((result) -> {
switch result {
case Success(pos):
handle.writeLine('Position x:${pos.x} y:${pos.y} z:${pos.z}');
} else {
handle.writeLine("Position not available");
case Failure(err):
handle.writeLine("Position not available: " + err);
}
return true;
});

View File

@@ -4,7 +4,7 @@ import kernel.peripherals.Peripherals.Peripheral;
import lib.CLIAppBase;
@:build(macros.Binstore.includeBin("Peripheral", ["ph", "peripheral"]))
class Perf extends CLIAppBase {
class Peri extends CLIAppBase {
public function new() {
registerSyncSubcommand("inspect", (args) -> {
var addr = args.getString("addr");

View File

@@ -1,5 +1,7 @@
package bin;
import kernel.turtle.Types.ToolSide;
import lib.turtle.Helper;
import kernel.turtle.TurtleMutex;
import kernel.turtle.Turtle;
import lib.turtle.InvManager;
@@ -11,28 +13,28 @@ using tink.CoreApi;
class TurtleCtl extends CLIAppBase {
public function new() {
registerAsyncSubcommand("f", (args) -> {
return asynPerform(Turtle.forward, args.getInt("times"));
}, [Int("times")]);
return asynPerform(Turtle.forward, args.getInt("times") ?? 1);
}, [Optional(Int("times"))]);
registerAsyncSubcommand("b", (args) -> {
return asynPerform(Turtle.back, args.getInt("times"));
}, [Int("times")]);
return asynPerform(Turtle.back, args.getInt("times") ?? 1);
}, [Optional(Int("times"))]);
registerAsyncSubcommand("l", (args) -> {
return asynPerform(Turtle.turnLeft, args.getInt("times"));
}, [Int("times")]);
return asynPerform(Turtle.turnLeft, args.getInt("times") ?? 1);
}, [Optional(Int("times"))]);
registerAsyncSubcommand("r", (args) -> {
return asynPerform(Turtle.turnRight, args.getInt("times"));
}, [Int("times")]);
return asynPerform(Turtle.turnRight, args.getInt("times") ?? 1);
}, [Optional(Int("times"))]);
registerAsyncSubcommand("u", (args) -> {
return asynPerform(Turtle.up, args.getInt("times"));
}, [Int("times")]);
return asynPerform(Turtle.up, args.getInt("times") ?? 1);
}, [Optional(Int("times"))]);
registerAsyncSubcommand("d", (args) -> {
return asynPerform(Turtle.down, args.getInt("times"));
}, [Int("times")]);
return asynPerform(Turtle.down, args.getInt("times") ?? 1);
}, [Optional(Int("times"))]);
registerAsyncSubcommand("defrag", (args) -> {
return asynPerform(() -> {
@@ -96,6 +98,47 @@ class TurtleCtl extends CLIAppBase {
return Success(null);
}, 1);
}, [Optional(String("to"))]);
registerAsyncSubcommand("dig", (args) -> {
var direction = args.getString("direction");
switch (direction) {
case "front" | "f":
return asynPerform(() -> Turtle.dig(Front), 1);
case "top" | "t" | "up" | "u":
return asynPerform(() -> Turtle.dig(Up), 1);
case "bot" | "bottom" | "b" | "down" | "d":
return asynPerform(() -> Turtle.dig(Down), 1);
default:
return Future.sync(false);
}
}, [String("direction")]);
registerAsyncSubcommand("tunnel", (args) -> {
var len = args.getInt("length");
return asynPerform(Helper.combine.bind([Turtle.digEmpty.bind(Front), Turtle.forward, Turtle.digEmpty.bind(Up)]), len);
}, [Int("length")]);
registerSyncSubcommand("inspect", (args) -> {
var res = Turtle.inspect(Front);
switch res {
case Failure(err):
handle.writeLine("Failed: " + err);
return false;
case Success(data):
handle.writeLine('Name: ${data.name}');
handle.writeLine("Tags:");
for (tag in data.tags.sortByName()) {
handle.writeLine(' ${tag}');
}
handle.writeLine("State:");
handle.writeLine(data.state);
}
return true;
}, []);
}
private function asynPerform(op:Void->Outcome<Noise, String>, times:Int):Future<Bool> {
@@ -134,18 +177,4 @@ class TurtleCtl extends CLIAppBase {
return null;
});
}
private function parseTimes(args:Array<String>):Int {
if (args.length > 0) {
var num = Std.parseInt(args[0]);
if (num == null) {
handle.writeLine("Failed to parse args");
return 0;
} else {
return num;
}
} else {
return 1;
}
}
}

View File

@@ -1,6 +1,6 @@
package bin.pathfinder;
import lib.Pos3;
import lib.WorldPos;
import lib.ui.elements.IUIElement;
import lib.ui.elements.TextElement;
import lib.ui.elements.RootElement;
@@ -36,7 +36,7 @@ class PFClient implements IProcess {
private function render() {
var acc = kernel.gps.GPS.getAccuracy();
var pos:Pos3 = kernel.gps.GPS.getPosition() ?? {x: 0, y: 0, z: 0};
var pos:WorldPos = kernel.gps.GPS.getPosition() ?? {x: 0, y: 0, z: 0};
var childre:Array<IUIElement> = [
new TextElement('Acc: ${acc}'),

View File

@@ -0,0 +1,56 @@
package bin.turtle;
import lib.turtle.planner.Plan;
import kernel.ps.ProcessHandle;
import kernel.ps.IProcess;
@:build(macros.Binstore.includeBin("Circle Mine Plan", ["cmp"]))
class CircleMinePlan implements IProcess {
public function new() {}
public function run(handle:ProcessHandle) {
var rings:Int = 3;
var skip:Int = 0;
var p = Plan.newPlan();
p = p.repeat([Forward], (skip * 3) - 1); // Move to outer ring or stay if none skiped
for (i in skip...(rings + skip)) {
if (i == 0) {
p = p.act([Clear(Down), Down, Clear(Down)]) // Move down a layer
.subplan(center());
} else {
p = p.subplan(circle(i * 3 + 1));
}
}
p.repeat([Back], (rings + skip) * 3 - 1);
p.begin(handle);
}
private function circle(radius:Int):Plan {
return Plan.newPlan()
.act([FullTunnel, FullTunnel, HalfTunnel])
.act([TurnRight])
.repeat([FullTunnel], radius)
.repateSubplan(Plan.newPlan().act([TurnRight]).repeat([FullTunnel], radius).act([
TurnRight, Clear(Front), Down, Clear(Front), TurnLeft, TurnLeft, Clear(Front), Up, Clear(Front), TurnRight
]).repeat([FullTunnel], radius), 3)
.act([TurnRight])
.repeat([FullTunnel], radius)
.act([TurnLeft, Forward]);
}
private function center():Plan {
return Plan.newPlan()
.act([Clear(Down)])
.act([FullTunnel])
.act([Clear(Front), Down, Clear(Front), Up])
.act([TurnRight])
.repeat([FullTunnel, TurnRight, FullTunnel, TurnLeft, HalfTunnel, TurnRight], 3)
.act([FullTunnel])
.act([TurnRight, Forward, TurnLeft, Forward]);
}
}

View File

@@ -4,7 +4,7 @@ import kernel.turtle.TurtleMutex;
import kernel.turtle.Turtle;
import kernel.peripherals.Peripherals.Peripheral;
import kernel.log.Log;
import lib.Pos;
import lib.ScreenPos;
import cc.HTTP.HTTPResponse;
import lua.TableTools;
import lua.Coroutine;
@@ -38,11 +38,11 @@ class KernelEvents {
distance:Null<Float>
}>;
public static var onMonitorResize(default, null):Signal<String>;
public static var onMonitorTouch(default, null):Signal<{addr:String, pos:Pos}>;
public static var onMouseClick(default, null):Signal<{button:ButtonType, pos:Pos}>;
public static var onMouseDrag(default, null):Signal<{button:ButtonType, pos:Pos}>;
public static var onMouseScroll(default, null):Signal<{dir:Int, pos:Pos}>;
public static var onMouseUp(default, null):Signal<{button:ButtonType, pos:Pos}>;
public static var onMonitorTouch(default, null):Signal<{addr:String, pos:ScreenPos}>;
public static var onMouseClick(default, null):Signal<{button:ButtonType, pos:ScreenPos}>;
public static var onMouseDrag(default, null):Signal<{button:ButtonType, pos:ScreenPos}>;
public static var onMouseScroll(default, null):Signal<{dir:Int, pos:ScreenPos}>;
public static var onMouseUp(default, null):Signal<{button:ButtonType, pos:ScreenPos}>;
public static var onPaste(default, null):Signal<String>;
public static var onPeripheral(default, null):Signal<String>;
public static var onPeripheralDetach(default, null):Signal<String>;
@@ -75,11 +75,11 @@ class KernelEvents {
distance:Null<Float>
}> = Signal.trigger();
private static final onMonitorResizeTrigger:SignalTrigger<String> = Signal.trigger();
private static final onMonitorTouchTrigger:SignalTrigger<{addr:String, pos:Pos}> = Signal.trigger();
private static final onMouseClickTrigger:SignalTrigger<{button:ButtonType, pos:Pos}> = Signal.trigger();
private static final onMouseDragTrigger:SignalTrigger<{button:ButtonType, pos:Pos}> = Signal.trigger();
private static final onMouseScrollTrigger:SignalTrigger<{dir:Int, pos:Pos}> = Signal.trigger();
private static final onMouseUpTrigger:SignalTrigger<{button:ButtonType, pos:Pos}> = Signal.trigger();
private static final onMonitorTouchTrigger:SignalTrigger<{addr:String, pos:ScreenPos}> = Signal.trigger();
private static final onMouseClickTrigger:SignalTrigger<{button:ButtonType, pos:ScreenPos}> = Signal.trigger();
private static final onMouseDragTrigger:SignalTrigger<{button:ButtonType, pos:ScreenPos}> = Signal.trigger();
private static final onMouseScrollTrigger:SignalTrigger<{dir:Int, pos:ScreenPos}> = Signal.trigger();
private static final onMouseUpTrigger:SignalTrigger<{button:ButtonType, pos:ScreenPos}> = Signal.trigger();
private static final onPasteTrigger:SignalTrigger<String> = Signal.trigger();
private static final onPeripheralTrigger:SignalTrigger<String> = Signal.trigger();
private static final onPeripheralDetachTrigger:SignalTrigger<String> = Signal.trigger();

View File

@@ -1,6 +1,6 @@
package kernel;
import lib.Pos;
import lib.ScreenPos;
import kernel.ui.ITermWriteable;
import cc.Term;
import lib.Vec.Vec2;
@@ -39,7 +39,7 @@ class MainTerm implements ITermWriteable {
Term.scroll(y);
}
public function getCursorPos():Pos {
public function getCursorPos():ScreenPos {
var rtn = Term.getCursorPos();
return {
x: rtn.x - 1,
@@ -60,7 +60,7 @@ class MainTerm implements ITermWriteable {
Term.setCursorBlink(blink);
}
public function getSize():Pos {
public function getSize():ScreenPos {
var rtn = Term.getSize();
return {
x: rtn.width,

View File

@@ -1,11 +1,12 @@
package kernel.gps;
import lib.SingleTimeoutPromise;
import kernel.log.Log;
import lib.KVStore;
import kernel.net.Net;
import kernel.net.INetworkInterface;
import kernel.net.Package;
import lib.Pos3;
import lib.WorldPos;
using tink.CoreApi;
@@ -15,20 +16,22 @@ using tink.CoreApi;
You need at least 3 computers that know their position to determine the position of the computer.
**/
class GPS {
private static inline final TIMEOUT:Int = 1;
private static var shouldRespond = true;
private static var shouldDoWholeNumberCheck = true;
private static var posAccuracy = 0; // 0 = unkown, 1 = (ins,best guess), 2 = (stored/manual,should be right), 3 = (gps,confirmed)
private static var cachedPosition:Pos3;
private static var lastPositionResponse:Array<{pos:Pos3, dist:Float}> = [];
private static var cachedPosition:WorldPos;
private static var lastPositionResponse:Array<{pos:WorldPos, dist:Float}> = [];
private static var futureResolve:(pos:Null<Pos3>) -> Void = null;
private static final locatePromise:SingleTimeoutPromise<WorldPos> = new SingleTimeoutPromise(TIMEOUT, brodcastPositionRequest);
@:allow(kernel.Init)
private static function init() {
loadCachedPosition();
}
public static function setManualPosition(pos:Pos3) {
public static function setManualPosition(pos:WorldPos) {
var kvstore = new KVStore("gps");
kvstore.set("mpos", pos);
kvstore.save();
@@ -40,12 +43,12 @@ class GPS {
}
@:allow(kernel.gps.INS)
private static function setINSPosition(pos:Pos3) {
private static function setINSPosition(pos:WorldPos) {
cachedPosition = pos;
posAccuracy = 1;
}
public static function getPosition():Null<Pos3> {
public static function getPosition():Null<WorldPos> {
return cachedPosition;
}
@@ -58,18 +61,8 @@ class GPS {
posAccuracy = 0;
}
public static function locate():Future<Null<Pos3>> {
// TODO: implenet a timeout
// TODO: dont send a request twice if the last one is still pending or we moved
return new Future<Null<Pos3>>((resolve) -> {
futureResolve = resolve;
sendPositionRequest();
return null;
});
}
private static function resolveFuture(pos:Null<Pos3>) {
futureResolve(pos);
public static function locate():Promise<WorldPos> {
return locatePromise.request();
}
private static function persistCachedPositon() {
@@ -85,8 +78,8 @@ class GPS {
var kvstore = new KVStore("gps");
kvstore.load();
var mPos:Null<Pos3> = kvstore.get("mpos"); // Manual set position
var cPos:Null<Pos3> = kvstore.get("cpos"); // Cached position
var mPos:Null<WorldPos> = kvstore.get("mpos"); // Manual set position
var cPos:Null<WorldPos> = kvstore.get("cpos"); // Cached position
if (mPos != null && cPos != null && mPos == cPos) {
// Both are the same, so we can use the cached position
@@ -117,12 +110,17 @@ class GPS {
}
}
private static function sendPositionRequest() {
private static function brodcastPositionRequest() {
Net.brodcastGPSRequest();
}
@:allow(kernel.net.Net)
private static function handlePackage(pack:Package<Noise>, dist:Float, iface:INetworkInterface) {
private static function handlePackage(pack:Package<Noise>, dist:Null<Float>, iface:INetworkInterface) {
if (dist == null) {
// Message comes from another dimension and has no distance.
return;
}
switch (pack.type) {
case GPSRequest:
if (!shouldRespond)
@@ -132,13 +130,13 @@ class GPS {
if (cachedPosition == null)
return;
var response = new Package(Net.networkID, pack.fromID, pack.msgID, GPSResponse(cachedPosition), null, 0);
var response = new Package(Net.networkID, pack.fromID, pack.msgID, GPSResponse(cachedPosition.x, cachedPosition.y, cachedPosition.z,), null, 0);
iface.send(pack.fromID, Net.networkID, response);
case GPSResponse(pos):
if (lastPositionResponse.contains({pos: pos, dist: dist}))
case GPSResponse(x, y, z):
if (lastPositionResponse.contains({pos: {x: x, y: y, z: z}, dist: dist}))
return; // Ignore duplicate responses
lastPositionResponse.push({pos: pos, dist: dist});
lastPositionResponse.push({pos: {x: x, y: y, z: z}, dist: dist});
// TODO: wait for a few seconds before calculating the position, so we can get more responses
@@ -158,12 +156,12 @@ class GPS {
cachedPosition = calculatedPosition;
posAccuracy = 3;
resolveFuture(calculatedPosition);
locatePromise.resolve(calculatedPosition);
default:
}
}
private static function calculatePosition():Null<Pos3> {
private static function calculatePosition():Null<WorldPos> {
if (lastPositionResponse.length < 3)
return null;
@@ -219,7 +217,7 @@ class GPS {
/**
Determines the position(s) of a point given 3 other points and the distance to each of them.
**/
private static function trilateration(p1:Pos3, p2:Pos3, p3:Pos3, r1:Float, r2:Float, r3:Float):Pair<Pos3, Pos3> {
private static function trilateration(p1:WorldPos, p2:WorldPos, p3:WorldPos, r1:Float, r2:Float, r3:Float):Pair<WorldPos, WorldPos> {
var a2b = p2 - p1;
var a2c = p3 - p1;
@@ -228,7 +226,7 @@ class GPS {
var i = ex.dot(a2c);
var ey = (a2c - ex * i).normalize();
var j = ey.dot(a2c);
var ez = ex.cross(ey);
var ez:WorldPos = ex.cross(ey);
var x = (r1 * r1 - r2 * r2 + d * d) / (2 * d);
var y = (r1 * r1 - r3 * r3 - x * x + (x - i) * (x - i) + j * j) / (2 * j);

View File

@@ -1,14 +1,15 @@
package kernel.gps;
import kernel.log.Log;
import lib.SinglePromise;
import kernel.turtle.Turtle;
import lib.Pos3;
import lib.WorldPos;
using tink.CoreApi;
class INS {
private static var heading:Null<Pos3> = null;
private static var heading:Null<WorldPos> = null;
private static var alingment:Int = 1; // 0 = degraded, 1 = not aligned, 2 = aligned
private static final alignPromise:SinglePromise<Noise> = new SinglePromise(startAlign);
@:allow(kernel.turtle.Turtle)
private static function moveForward() {
@@ -68,7 +69,10 @@ class INS {
}
}
private static function move(dir:Null<Pos3>) {
private static function move(dir:Null<WorldPos>) {
if (alignPromise.isRunning()) {
return;
}
var pos = GPS.getPosition();
if (pos == null || dir == null)
return;
@@ -76,47 +80,42 @@ class INS {
GPS.setINSPosition(newPos);
}
public static function getHeading():Null<Pos3> {
public static function getHeading():Null<WorldPos> {
return heading;
}
public static function align():Promise<Noise> {
Log.info("Aligning INS");
return new Promise<Noise>((resolve, reject) -> {
if (Turtle.getFuelLevel() < 2) {
Log.warn("Not enough fuel to align");
reject(new Error("Not enough fuel to align"));
return null;
return alignPromise.request();
}
GPS.locate().handle((pos1) -> {
Log.debug('pos1: $pos1');
if (pos1 == null) {
Log.warn("GPS not available for 1st position");
reject(new Error("GPS not available"));
public static function startAlign():Void {
if (Turtle.getFuelLevel() < 2) {
alignPromise.reject(new Error("Not enough fuel to align"));
return;
}
GPS.locate().handle((result) -> {
switch result {
case Failure(err):
alignPromise.reject(new Error("Failed to locate 1st positon: " + err));
return;
case Success(pos1):
var moved = tryMoving();
if (moved == -1) {
Log.warn("Can't move");
reject(new Error("Can't move"));
alignPromise.reject(new Error("Can't move"));
return;
}
GPS.locate().handle((pos2) -> {
Log.debug('pos2: $pos2');
if (pos2 == null) {
Log.warn("GPS not available for 2nd position");
reject(new Error("GPS not available for 2nd position"));
GPS.locate().handle((result2) -> {
switch result2 {
case Failure(err):
alignPromise.reject(new Error("GPS not available for 2nd position: " + err));
return;
}
case Success(pos2):
var cHeading = calcHeading(pos1, pos2, moved);
if (cHeading == null) {
Log.error("Can't calculate heading");
reject(new Error("Can't calculate heading"));
alignPromise.reject(new Error("Can't calculate heading"));
return;
}
@@ -124,10 +123,10 @@ class INS {
moveBack(moved);
GPS.setINSPosition(pos1);
resolve(Noise);
alignPromise.resolve(Noise);
}
});
});
return null;
}
});
}
@@ -150,7 +149,7 @@ class INS {
}
}
private static function calcHeading(pos1:Pos3, pos2:Pos3, moved:Int):Null<Pos3> {
private static function calcHeading(pos1:WorldPos, pos2:WorldPos, moved:Int):Null<WorldPos> {
if (moved == 0) {
return pos1 - pos2;
} else if (moved == 1) {
@@ -167,24 +166,18 @@ class INS {
private static function moveBack(moved:Int) {
if (moved == 0) {
Turtle.forward();
// cc.Turtle.forward();
} else if (moved == 1) {
Turtle.back();
// cc.Turtle.back();
} else if (moved == 2) {
Turtle.back();
// cc.Turtle.back();
Turtle.turnRight();
// cc.Turtle.turnRight();
} else if (moved == 3) {
Turtle.forward();
// cc.Turtle.forward();
Turtle.turnRight();
// cc.Turtle.turnRight();
}
}
private static function rotatePos3ToRight(pos:Pos3):Pos3 {
private static function rotatePos3ToRight(pos:WorldPos):WorldPos {
if (pos.x == 0 && pos.z == -1) {
return {x: 1, y: 0, z: 0};
} else if (pos.x == -1 && pos.z == 0) {
@@ -198,7 +191,7 @@ class INS {
}
}
private static function rotatePos3ToLeft(pos3:Pos3):Pos3 {
private static function rotatePos3ToLeft(pos3:WorldPos):WorldPos {
return rotatePos3ToRight(rotatePos3ToRight(rotatePos3ToRight(pos3)));
}
}

View File

@@ -1,7 +1,8 @@
package kernel.log;
import haxe.ds.ReadOnlyArray;
#if webconsole
import haxe.macro.Context;
#if (webconsole && !macro)
import lib.Debug;
#end
@@ -24,35 +25,59 @@ class Log {
#if debug
haxe.Log.trace = function(v:Dynamic, ?infos:haxe.PosInfos) {
Log.debug(v, infos);
Log._debug(v, infos.className);
}
#end
}
public static function info(msg:Dynamic, ?pos:haxe.PosInfos) {
log({level: Info, message: Std.string(msg), time: 0}, pos);
public static function _info(msg:Dynamic, ?origin:String) {
log({
level: Info,
message: Std.string(msg),
time: 0,
origin: origin
});
}
public static function warn(msg:Dynamic, ?pos:haxe.PosInfos) {
log({level: Warn, message: Std.string(msg), time: 0}, pos);
public static function _warn(msg:Dynamic, ?origin:String) {
log({
level: Warn,
message: Std.string(msg),
time: 0,
origin: origin
});
}
public static function error(msg:Dynamic, ?pos:haxe.PosInfos) {
log({level: Error, message: Std.string(msg), time: 0}, pos);
public static function _error(msg:Dynamic, ?origin:String) {
log({
level: Error,
message: Std.string(msg),
time: 0,
origin: origin
});
}
public static function debug(msg:Dynamic, ?pos:haxe.PosInfos) {
public static function _debug(msg:Dynamic, ?origin:String) {
#if debug
log({level: Debug, message: Std.string(msg), time: 0}, pos);
log({
level: Debug,
message: Std.string(msg),
time: 0,
origin: origin
});
#end
}
public static function silly(msg:Dynamic, ?pos:haxe.PosInfos) {
log({level: Silly, message: Std.string(msg), time: 0}, pos);
public static function _silly(msg:Dynamic, ?origin:String) {
log({
level: Silly,
message: Std.string(msg),
time: 0,
origin: origin
});
}
private static function log(line:LogLine, ?pos:haxe.PosInfos) {
line.origin = pos.className;
private static function log(line:LogLine) {
logLines.push(line);
if (logLines.length > MAX_LINES) {
@@ -60,12 +85,49 @@ class Log {
}
onLogTrigger.trigger(line);
#if webconsole
Debug.printWeb('[${Std.string(line.level)}][${line.origin}] ${line.message}');
#if (webconsole && !macro)
Debug.logToWebconsole(line);
#end
}
public static function getLines():ReadOnlyArray<LogLine> {
return logLines;
}
#if macro
private static function getOrigin():String {
var localClass = Context.getLocalClass().get();
return localClass.name;
}
#end
public macro static function info(msg:ExprOf<Dynamic>) {
return macro {
Log._info(${msg}, $v{getOrigin()});
}
}
public macro static function warn(msg:ExprOf<Dynamic>) {
return macro {
Log._warn(${msg}, $v{getOrigin()});
}
}
public macro static function error(msg:ExprOf<Dynamic>) {
return macro {
Log._error(${msg}, $v{getOrigin()});
}
}
public macro static function debug(msg:ExprOf<Dynamic>) {
return macro {
Log._debug(${msg}, $v{getOrigin()});
}
}
public macro static function silly(msg:ExprOf<Dynamic>) {
return macro {
Log._silly(${msg}, $v{getOrigin()});
}
}
}

View File

@@ -39,6 +39,35 @@ class Net {
setupInterf(interf);
}
// Handle when a modem has been removed
KernelEvents.onPeripheralDetach.handle((addr) -> {
var idx = interfaces.findIndex((i) -> {
return i.name() == addr;
});
if (idx == -1) {
return;
}
Log.debug('Removed modem on addr: $addr');
interfaces.splice(idx, 1);
Routing.removeInterface(addr);
});
// Handle when a new modem is connected
KernelEvents.onPeripheral.handle((addr) -> {
if (!Peripheral.getTypes(addr).contains("modem")) {
return;
}
Log.debug('Added modem on addr: $addr');
var modem = Peripheral.getModem(addr);
interfaces.push(modem);
setupInterf(modem);
Routing.addInterface(modem);
});
setupPingHandle();
Routing.setup();
@@ -62,6 +91,9 @@ class Net {
});
}
/**
Enables the interface to receive messages.
**/
private static function setupInterf(interf:INetworkInterface) {
interf.onMessage.handle(e -> handle(e.pack, interf, e.dist));
interf.listen(networkID);
@@ -135,12 +167,13 @@ class Net {
if (!sendRaw(pack)) {
// Cant forward
Log.silly('Received a message that could not be forwarded to ${pack.toID}');
}
}
public static function respondTo(pack:GenericPackage, data:Dynamic) {
if (pack.type.match(DataNoResponse(_))) {
Log.warn("Responed to a no response package. Ignoring");
if (!pack.type.match(Data(_))) {
Log.warn("Responding to a package that does require responding. Ignoring.");
return;
}
@@ -212,6 +245,8 @@ class Net {
if (timeout != null) {
timeout.cancle();
}
responseBus.remove(pack.msgID);
});
timeout = new Timer(MESSAGE_TIMEOUT, () -> {

View File

@@ -1,7 +1,5 @@
package kernel.net;
import lib.Pos3;
typedef NetworkID = Int;
typedef GenericPackage = Package<Dynamic>;
@@ -12,7 +10,7 @@ enum PackageTypes {
RouteDiscover(reachableIDs:Array<{id:NetworkID, cost:Int}>);
RouteDiscoverResponse(reachableIDs:Array<{id:NetworkID, cost:Int}>);
RouteDiscoverUpdate(reachableIDs:Array<{id:NetworkID, cost:Int}>);
GPSResponse(pos:Pos3);
GPSResponse(x:Float, y:Float, z:Float);
GPSRequest();
}

View File

@@ -74,30 +74,17 @@ class Routing {
private static function handleRoutePackage(pack:Package<Noise>, interf:INetworkInterface):Void {
addPossibleRoute(pack.fromID, interf, 0, pack.fromID);
var shouldRespond:Bool = switch pack.type {
switch pack.type {
case RouteDiscoverResponse(routes):
// A request for routes has been answerd
for (route in routes) {
addPossibleRoute(route.id, interf, route.cost, pack.fromID);
}
false;
case RouteDiscover(routes):
// Received a request for available routes
for (route in routes) {
addPossibleRoute(route.id, interf, route.cost, pack.fromID);
}
true;
case RouteDiscoverUpdate(routes):
for (route in routes) {
addPossibleRoute(route.id, interf, route.cost, pack.fromID);
}
false;
default:
Log.error("Expected package to be a Route discover package");
false;
};
if (!shouldRespond) {
return;
}
// Respond to peer
var response:Package<Noise> = {
@@ -110,6 +97,14 @@ class Routing {
}
interf.send(response.toID, Net.networkID, response);
case RouteDiscoverUpdate(routes):
// Received an update of routes
for (route in routes) {
addPossibleRoute(route.id, interf, route.cost, pack.fromID);
}
default:
Log.silly("Expected package to be a Route discover package");
};
}
/**
@@ -126,6 +121,9 @@ class Routing {
return routes;
}
/**
Create a new packate to brodcast our routes.
**/
private static function newRoutDiscoverPackage():Package<Noise> {
var pack:Package<Noise> = {
type: RouteDiscover(genRouteList()),
@@ -179,4 +177,28 @@ class Routing {
public static function getRouteTable():Map<NetworkID, Route> {
return routingTable;
}
/**
Call when an interface is no longer available.
Will rebrodcast routing requests.
**/
@:allow(kernel.net.Net)
private static function removeInterface(addr:String) {
// Remove the interface from the routing table
for (to => modem in routingTable) {
if (modem.interf.name() == addr) {
routingTable.remove(to);
}
}
// Since some routes to some host could be lost
// we rebrodcast on all interfaces again
brodcastRoutingTable();
}
@:allow(kernel.net.Net)
private static function addInterface(iface:INetworkInterface) {
var pack = newRoutDiscoverPackage();
iface.send(Net.BRODCAST_PORT, Net.networkID, pack);
}
}

View File

@@ -0,0 +1,76 @@
package kernel.peripherals;
import lib.BlockPos;
import lib.Tags;
import cc.Peripheral;
using tink.CoreApi;
using lua.Table;
@:structInit typedef ScanBlock = {
name:String,
tags:Tags,
pos:BlockPos
}
class GeoScanner implements IPeripheral {
public static inline final TYPE_NAME:String = "geoScanner";
private final addr:String;
public function new(addr:String) {
this.addr = addr;
}
public function getAddr():String {
return this.addr;
}
public function getType():String {
return TYPE_NAME;
}
/**
Returns the current time remaining until then next scan() can be ran.
**/
public function getScanCooldown():Int {
return Peripheral.call(this.addr, "getScanCooldown");
}
/**
Returns the cost in FE for a scan with the given radius.
Returns null of scan of this radius is not posible.
**/
public function cost(radius:Int):Null<Int> {
return Peripheral.call(this.addr, "cost", radius);
}
/**
Returns a list of data about all blocks in the radius.
X,Y,Z are relative to the geoscanner block.
Air blocks are not included.
List is unsorted.
**/
public function scan(radius:Int):Outcome<Array<ScanBlock>, String> {
// TODO: Handel fail state
var result:lua.Table<Int, {
x:Int,
y:Int,
z:Int,
name:String,
tags:lua.Table<Int, String>
}> = Peripheral.call(this.addr, "scan", radius);
return Success(result.toArray().map((e) -> {
name: e.name,
tags: Tags.fromIntTable(e.tags),
pos: new BlockPos(e.x, e.y, e.z)
}));
}
public function chunkAnalyze():Outcome<Map<String, Int>, String> {
var result:lua.Table<String, Int> = Peripheral.call(this.addr, "chunkAnalyze");
return Success(result.toMap());
}
}

View File

@@ -0,0 +1,123 @@
package kernel.peripherals;
import kernel.log.Log;
import lib.Debug;
import cc.Peripheral;
import lib.Tags;
import lib.Item;
import cc.periphs.ItemStorage.ReducedItemInfo;
import lua.Table;
using Lambda;
class ItemInfo {
public final count:Int;
public final name:Item;
public final nbt:Null<String>;
@:allow(kernel.peripherals.Inventory)
private function new(from:ReducedItemInfo) {
this.count = from.count;
this.name = from.name;
this.nbt = from.nbt;
}
}
class DetailedItemInfo extends ItemInfo {
public final displayName:String;
public final maxCount:Int;
public final tags:Tags;
public final durability:Null<Float>;
public final damage:Null<Int>;
public final maxDamage:Null<Int>;
public final enchantments:Array<{displayName:String, level:Int, name:String}>;
@:allow(kernel.peripherals.Inventory)
private function new(from:cc.periphs.ItemStorage.DetailedItemInfo) {
super(from);
this.tags = Tags.fromBoolTable(from.tags);
this.displayName = from.displayName;
this.maxCount = from.maxCount;
this.durability = from.durability;
this.damage = from.damage;
this.maxDamage = from.maxDamage;
if (from.enchantments != null) {
this.enchantments = from.enchantments;
} else {
this.enchantments = [];
}
}
}
class Inventory implements IPeripheral {
public static inline final TYPE_NAME:String = "inventory";
private final addr:String;
public function new(addr:String) {
this.addr = addr;
}
public function getAddr():String {
return this.addr;
}
public function getType():String {
return TYPE_NAME;
}
/**
Get the size of this inventory.
**/
public function size():Int {
return Peripheral.call(this.addr, "size");
}
/**
List all items in this inventory.
**/
public function list():Map<Int, ItemInfo> {
var list:Map<Int, ReducedItemInfo> = Table.toMap(Peripheral.call(this.addr, "list"));
var rtn:Map<Int, ItemInfo> = new Map();
for (k => v in list) {
rtn.set(k - 1, new ItemInfo(v));
}
return rtn;
}
/**
Get detailed information about an item.
**/
public function getItemDetail(slot:Int):DetailedItemInfo {
return new DetailedItemInfo(Peripheral.call(this.addr, "getItemDetail", slot + 1));
}
/**
Get the maximum number of items which can be stored in this slot.
Typically this will be limited to 64 items.
However, some inventorwies (such as barrels or caches) can store hundreds or thousands of items in one slot.
Keep in mind that this does not depend on that item stored in this slot.
**/
public function getItemLimit(slot:Int):Int {
return Peripheral.call(this.addr, "getItemLimit", slot + 1);
}
/**
Push items from one inventory to another connected one. Needs to be on the same wired network.
**/
public function pushItems(to:String, fromSlot:Int, ?limit:Int, ?toSlot:Int):Int {
return Peripheral.call(this.addr, "pushItems", to, fromSlot + 1, limit, toSlot);
}
/**
Pull items from a connected inventory into this one. Needs to be on the same wired network.
**/
public function pullItems(from:String, fromSlot:Int, ?limit:Int, ?toSlot:Int):Int {
return Peripheral.call(this.addr, "pullItems", from, fromSlot + 1, limit, toSlot);
}
}

View File

@@ -1,6 +1,5 @@
package kernel.peripherals;
import kernel.log.Log;
import kernel.peripherals.Modem;
import kernel.peripherals.Screen;
@@ -81,6 +80,10 @@ class Peripheral {
return getSpeaker(addr);
case Redstone.TYPE_NAME:
return getRedstone(addr);
case Inventory.TYPE_NAME:
return getInventory(addr);
case GeoScanner.TYPE_NAME:
return getGeoScanner(addr);
}
return null;
@@ -167,4 +170,26 @@ class Peripheral {
public static function getAllSpeakers():Array<Speaker> {
return [for (addr in findAddrByType(Speaker.TYPE_NAME)) new Speaker(addr)];
}
public static function getInventory(addr:String):Null<Inventory> {
var addr = safeGetAddr(addr, Inventory.TYPE_NAME);
if (addr == null)
return null;
return new Inventory(addr);
}
public static function getAllInventorys():Array<Inventory> {
return [for (addr in findAddrByType(Inventory.TYPE_NAME)) new Inventory(addr)];
}
public static function getGeoScanner(addr:String):Null<GeoScanner> {
var addr = safeGetAddr(addr, GeoScanner.TYPE_NAME);
if (addr == null)
return null;
return new GeoScanner(addr);
}
public static function getAllGeoScanners():Array<GeoScanner> {
return [for (addr in findAddrByType(GeoScanner.TYPE_NAME)) new GeoScanner(addr)];
}
}

View File

@@ -2,7 +2,7 @@ package kernel.peripherals;
import cc.Peripheral;
import lib.Rect;
import lib.Pos;
import lib.ScreenPos;
class Printer implements IPeripheral {
public static inline final TYPE_NAME:String = "printer";
@@ -25,11 +25,11 @@ class Printer implements IPeripheral {
public function write(text:String) {}
public function getCurserPos():Pos {
return new Pos({x: 0, y: 0});
public function getCurserPos():ScreenPos {
return new ScreenPos({x: 0, y: 0});
}
public function setCurserPos(pos:Pos) {
public function setCurserPos(pos:ScreenPos) {
this.native.setCursorPos(pos.x, pos.y);
}

View File

@@ -1,7 +1,7 @@
package kernel.peripherals;
import cc.Peripheral;
import lib.Pos;
import lib.ScreenPos;
import cc.Term.TerminalSize;
import kernel.ui.ITermWriteable;
import lib.Vec.Vec2;
@@ -59,7 +59,7 @@ class Screen implements ITermWriteable implements IPeripheral {
nativ.scroll(y);
}
public function getCursorPos():Pos {
public function getCursorPos():ScreenPos {
var rtn = nativ.getCursorPos();
return {
x: rtn.x - 1,

View File

@@ -72,6 +72,11 @@ class Turtle {
return r2;
}
/**
Dig in the provided direction.
Keep in mind that digging on a empty space is considerd a failure.
Also see `digEmpty`.
**/
public static function dig(dir:InteractDirections, ?toolSide:ToolSide):Outcome<Noise, String> {
var r:cc.Turtle.TurtleActionResult;
@@ -87,6 +92,36 @@ class Turtle {
return conterToOutcome(r);
}
/**
Dig in the provided direction.
Does not fail if there is nothing to dig.
Also see `dig`.
**/
public static function digEmpty(dir:InteractDirections, ?toolSide:ToolSide):Outcome<Noise, String> {
var r:cc.Turtle.TurtleActionResult;
// FIXME: upstream needs to be fixed to accept ToolSide
switch dir {
case Front:
r = cc.Turtle.dig();
case Up:
r = cc.Turtle.digUp();
case Down:
r = cc.Turtle.digDown();
}
var result = conterToOutcome(r);
switch (result) {
case Success(_):
return result;
case Failure(failure):
if (failure == "Nothing to dig here") {
return Success(null);
}
return result;
}
}
public static function place(dir:InteractDirections):Outcome<Noise, String> {
var r:cc.Turtle.TurtleActionResult;
switch dir {

View File

@@ -1,6 +1,6 @@
package kernel.ui;
import lib.Pos;
import lib.ScreenPos;
import lib.Vec.Vec2;
import lib.Color;
import kernel.ui.ITermWriteable;
@@ -86,7 +86,7 @@ class BufferedVirtualTermWriter implements IVirtualTermWriter extends TermBuffer
super.scroll(y);
}
public override function getCursorPos():Pos {
public override function getCursorPos():ScreenPos {
if (isEnabled()) {
return target.getCursorPos();
} else {

View File

@@ -1,6 +1,6 @@
package kernel.ui;
import lib.Pos;
import lib.ScreenPos;
import lib.Color;
import lib.Vec.Vec2;
@@ -18,7 +18,7 @@ interface ITermWriteable {
/**
Even though CC is 1 based we use a 0 based index.
**/
public function getCursorPos():Pos;
public function getCursorPos():ScreenPos;
/**
Even though CC is 1 based we use a 0 based index.

View File

@@ -1,7 +1,7 @@
package kernel.ui;
import kernel.log.Log;
import lib.Pos;
import lib.ScreenPos;
import lib.Vec.Vec2;
import lib.Color;
@@ -110,8 +110,8 @@ class StatelessVirtualTermWriter implements IVirtualTermWriter {
target.scroll(y);
}
public inline function getCursorPos():Pos {
return enabled ? target.getCursorPos() : new Pos({x: 0, y: 0});
public inline function getCursorPos():ScreenPos {
return enabled ? target.getCursorPos() : new ScreenPos({x: 0, y: 0});
}
public inline function setCursorPos(x:Int, y:Int) {

View File

@@ -1,6 +1,6 @@
package kernel.ui;
import lib.Pos;
import lib.ScreenPos;
import lib.Vec.Vec2;
import lib.Color;
import kernel.ui.ITermWriteable;
@@ -18,7 +18,7 @@ class TermBuffer implements ITermWriteable {
**/
private var screenBuffer:Array<Array<Pixel>>;
private var cursorPos:Pos = {x: 0, y: 0};
private var cursorPos:ScreenPos = {x: 0, y: 0};
private var currentTextColor:Color = White;
private var currentBgColor:Color = Black;
private var cursorBlink:Bool = false;
@@ -91,7 +91,7 @@ class TermBuffer implements ITermWriteable {
target.setCursorBlink(cursorBlink);
}
private function safeWriteScreenBuffer(pos:Pos, char:String) {
private function safeWriteScreenBuffer(pos:ScreenPos, char:String) {
if (screenBuffer.length > pos.y && screenBuffer[pos.y].length > pos.x) {
screenBuffer[pos.y][pos.x].char = char;
screenBuffer[pos.y][pos.x].bg = currentBgColor;
@@ -121,7 +121,7 @@ class TermBuffer implements ITermWriteable {
]);
}
public function getCursorPos():Pos {
public function getCursorPos():ScreenPos {
return cursorPos;
}

View File

@@ -1,7 +1,7 @@
package kernel.ui;
import lib.ui.rendere.IUIEventDelegate;
import lib.Pos;
import lib.ScreenPos;
import lib.Color;
import kernel.ButtonType;
import lib.Vec.Vec2;
@@ -17,21 +17,21 @@ class WindowContext implements ITermWriteable {
@:allow(kernel.ui.WindowManager) private var eventDelegate:Null<IUIEventDelegate>;
public var onClick(default, null):Signal<{button:ButtonType, pos:Pos}>;
public var onClick(default, null):Signal<{button:ButtonType, pos:ScreenPos}>;
public var onKey(default, null):Signal<{keyCode:Int, isHeld:Bool}>;
public var onKeyUp(default, null):Signal<Int>;
public var onMouseDrag(default, null):Signal<{button:ButtonType, pos:Pos}>;
public var onMouseScroll(default, null):Signal<{dir:Int, pos:Pos}>;
public var onMouseUp(default, null):Signal<{button:ButtonType, pos:Pos}>;
public var onMouseDrag(default, null):Signal<{button:ButtonType, pos:ScreenPos}>;
public var onMouseScroll(default, null):Signal<{dir:Int, pos:ScreenPos}>;
public var onMouseUp(default, null):Signal<{button:ButtonType, pos:ScreenPos}>;
public var onPaste(default, null):Signal<String>;
public var onChar(default, null):Signal<String>;
@:allow(kernel.ui.WindowManager) private final onClickTrigger:SignalTrigger<{button:ButtonType, pos:Pos}>;
@:allow(kernel.ui.WindowManager) private final onClickTrigger:SignalTrigger<{button:ButtonType, pos:ScreenPos}>;
@:allow(kernel.ui.WindowManager) private final onKeyTrigger:SignalTrigger<{keyCode:Int, isHeld:Bool}>;
@:allow(kernel.ui.WindowManager) private final onKeyUpTrigger:SignalTrigger<Int>;
@:allow(kernel.ui.WindowManager) private final onMouseDragTrigger:SignalTrigger<{button:ButtonType, pos:Pos}>;
@:allow(kernel.ui.WindowManager) private final onMouseScrollTrigger:SignalTrigger<{dir:Int, pos:Pos}>;
@:allow(kernel.ui.WindowManager) private final onMouseUpTrigger:SignalTrigger<{button:ButtonType, pos:Pos}>;
@:allow(kernel.ui.WindowManager) private final onMouseDragTrigger:SignalTrigger<{button:ButtonType, pos:ScreenPos}>;
@:allow(kernel.ui.WindowManager) private final onMouseScrollTrigger:SignalTrigger<{dir:Int, pos:ScreenPos}>;
@:allow(kernel.ui.WindowManager) private final onMouseUpTrigger:SignalTrigger<{button:ButtonType, pos:ScreenPos}>;
@:allow(kernel.ui.WindowManager) private final onPasteTrigger:SignalTrigger<String>;
@:allow(kernel.ui.WindowManager) private final onCharTrigger:SignalTrigger<String>;
@@ -89,7 +89,7 @@ class WindowContext implements ITermWriteable {
writer.scroll(y);
}
public inline function getCursorPos():Pos {
public inline function getCursorPos():ScreenPos {
return writer.getCursorPos();
}

12
src/lib/Block.hx Normal file
View File

@@ -0,0 +1,12 @@
package lib;
@:structInit
class Block {
public final name:PaN;
public final tags:Tags;
public function new(name:String, tags:Tags) {
this.name = name;
this.tags = tags;
}
}

114
src/lib/BlockBlockMap.hx Normal file
View File

@@ -0,0 +1,114 @@
package lib;
import kernel.peripherals.GeoScanner.ScanBlock;
import lib.BlockMap;
@:forward
abstract BlockBlockMap(BlockMap<Block>) {
public inline function new() {
this = new BlockMap<Block>();
}
public static function fromScan(scan:Array<ScanBlock>):BlockBlockMap {
var map:BlockBlockMap = new BlockBlockMap();
for (block in scan) {
map.set(block.pos, new Block(block.name, block.tags));
}
return map;
}
/**
Navigate from one point into another.
Returns the path to take.
Based on A*
**/
public function nav(from:BlockPos, to:BlockPos):Array<BlockPos> {
if (!canPass(to)) {
trace("End is blocked");
return [];
}
var openSet = new PriorityQueue();
openSet.insert(from, 0);
var cameFrom:BlockMap<BlockPos> = new BlockMap();
var gCost:BlockMap<Int> = new BlockMap(); // G cost is the distance from start
gCost.set(from, 0);
var fCost:BlockMap<Float> = new BlockMap(); // F cost is the combines cost of moving to this pos (G cost) + the distnace to the end
fCost.set(from, from.distance(to));
while (!openSet.isEmpty()) {
var current = openSet.extractMin(); // Check the pos with the lowest F cost
if (current.equals(to)) {
var totalPath = [];
var currentPathPos = to;
while (cameFrom.exists(currentPathPos)) {
totalPath.unshift(currentPathPos);
currentPathPos = cameFrom.get(currentPathPos);
}
totalPath.unshift(from);
return totalPath;
}
for (neighbor in current.neighbors()) {
if (!canPass(neighbor)) {
continue;
}
var newGCost = gCost.get(current) + 1; // Movment cost to neighbor is always 1 as we can't move diagonally
if (newGCost < (gCost.get(neighbor) ?? 9999)) {
cameFrom.set(neighbor, current);
gCost.set(neighbor, newGCost);
fCost.set(neighbor, newGCost + neighbor.distance(to));
if (!openSet.containsElement(neighbor)) {
openSet.insert(neighbor, fCost.get(neighbor));
}
}
}
}
return [];
}
/**
Some block can be passed by the turtle.
Some blocks get destroyed by it.
**/
public function canPass(k:BlockPos):Bool {
var block = this.get(k);
if (block == null) {
return true;
}
switch block.name {
case "minecraft:water":
return true;
case "minecaft:lava":
case "mimecraft:grass":
case "minecraft:hanging_roots":
case "minecraft:warped_roots":
case "minecraft:dead_bush":
case "minecraft:fern":
case "minecraft:vine":
case "minecraft:glow_lichen":
case "minecraft:seagrass":
case "minecraft:crimson_roots":
case "minecraft:nether_sprouts":
case "minecraft:snow":
case "minecraft:rose_bush":
case "minecraft:fire":
return true;
}
return false;
}
}

48
src/lib/BlockMap.hx Normal file
View File

@@ -0,0 +1,48 @@
package lib;
import haxe.ds.IntMap;
/**
Map values to positions in 3D space. For representing blocks from a scan see `BlockBlockMap`.
The only reason this exsists is that the abstract class for BlockPos does not
satisfy the constraint of HashMap. The hashCode function needs to be on a real class and not on
an abstract class. Thanks Obama.
**/
@:forward(iterator, clear)
abstract BlockMap<T>(IntMap<T>) {
/**
Creates a new HashMap.
**/
public inline function new() {
this = new IntMap();
}
/**
See `Map.set`
**/
@:arrayAccess public inline function set(k:BlockPos, v:T) {
this.set(k.hashCode(), v);
}
/**
See `Map.get`
**/
@:arrayAccess public inline function get(k:BlockPos) {
return this.get(k.hashCode());
}
/**
See `Map.exists`
**/
public inline function exists(k:BlockPos) {
return this.exists(k.hashCode());
}
/**
See `Map.remove`
**/
public inline function remove(k:BlockPos) {
return this.remove(k.hashCode());
}
}

78
src/lib/BlockPos.hx Normal file
View File

@@ -0,0 +1,78 @@
package lib;
import lib.Vec.Vec2;
import lib.Vec.Vec3;
/**
Represents a Point in a 3D `Int` System.
Basicly a wrapper for Vec3<Int> with some extra functions.
`Y` represents the height of the point.
**/
@:forward
abstract BlockPos(Vec3<Int>) from Vec3<Int> to Vec3<Int> {
public inline function new(x:Int, y:Int, z:Int) {
this = new Vec3(x, y, z);
}
@:op(A + B)
public inline function add(rhs:BlockPos):BlockPos {
return this.add(rhs);
}
@:op(A - B)
public inline function sub(rhs:BlockPos):BlockPos {
return this.sub(rhs);
}
@:op(A * B)
public inline function multiplyScalar(rhs:Int):BlockPos {
return this.multiplyScalar(rhs);
}
@:op(-A)
public inline function negate():BlockPos {
return this.negate();
}
@:op(A == B)
public function equals(rhs:BlockPos):Bool {
return this.x == rhs.x && this.y == rhs.y && this.z == rhs.z;
}
@:op(A != B)
public function notEquals(rhs:BlockPos):Bool {
return !equals(rhs);
}
public function hashCode():Int {
return (this.x * 73856093) ^ (this.y * 19349663) ^ (this.z * 83492791);
}
public function toString():String {
return 'BlockPos(${this.x}, ${this.y}, ${this.z})';
}
/**
Returns the chunk the position is in.
**/
public function chunk():Vec2<Int> {
var x = Math.floor(this.x / 16);
var z = Math.floor(this.z / 16);
return new Vec2(x, z);
}
/**
Returns a list of positions neighboring the block. No Diagonal.
**/
public function neighbors():Array<BlockPos> {
return [
new BlockPos(this.x + 1, this.y + 0, this.z + 0), // Front
new BlockPos(this.x - 1, this.y + 0, this.z + 0), // Back
new BlockPos(this.x + 0, this.y + 0, this.z - 1), // Left
new BlockPos(this.x + 0, this.y + 0, this.z + 1), // Right
new BlockPos(this.x + 0, this.y - 1, this.z + 0), // Bot
new BlockPos(this.x + 0, this.y + 1, this.z + 0), // Top
];
}
}

View File

@@ -1,7 +1,7 @@
package lib;
import kernel.http.HTTPRequest.Http;
import lua.TableTools;
import kernel.KernelEvents;
import kernel.log.Log;
import lua.NativeStringTools;
import lib.ui.Canvas;
@@ -9,6 +9,7 @@ import cc.ComputerCraft;
#if webconsole
import cc.HTTP;
import kernel.net.Net;
import kernel.log.LogLine;
#end
class Debug {
@@ -66,8 +67,8 @@ class Debug {
#end
#if webconsole
public static function printWeb(msg:String) {
HTTP.request("http://127.0.0.1:8080/" + Net.networkID, msg);
public static function logToWebconsole(line:LogLine) {
Http.request('http://127.0.0.1:8080/log/${Net.networkID}/${line.level.getIndex()}', '[${line.origin}] ${line.message}').eager();
}
#end
}

View File

@@ -1,69 +0,0 @@
package lib;
import haxe.Exception;
class LambdaExtender {
/**
Returns the first element if there are exectly one element present.
Throws exception if not.
**/
static public function single<T>(it:Iterable<T>):T {
var elem:T = null;
for (t in it) {
if (elem != null) {
throw new Exception("Multiple elements found");
}
elem = t;
}
if (elem == null) {
throw new Exception("No element found");
}
return elem;
}
/**
Like `single` but when no element was found return the default value.
**/
static public function singleOrDefault<T>(it:Iterable<T>, defaultValue:T):T {
var elem:T = null;
for (t in it) {
if (elem != null) {
throw new Exception("Multiple elements found");
}
elem = t;
}
if (elem == null) {
return defaultValue;
}
return elem;
}
/**
Returns the first element.
Throws execption if no first element found.
**/
static public function first<T>(it:Iterable<T>):T {
for (t in it) {
return t;
}
throw new Exception("No element found");
}
/**
Like `first` only if no first element was found it returns the defalt value.
**/
static public function firstOrDefault<T>(it:Iterable<T>, defaultValue:T):T {
var iter = it.iterator();
if (iter.hasNext()) {
return iter.next();
}
return defaultValue;
}
}

View File

@@ -3,7 +3,8 @@ package lib;
/**
Represents an item in the game.
**/
enum abstract Item(String) to String {
@:forward
enum abstract Item(PaN) to String {
public inline function new(name:String) {
// Check if the name is valid. in the format `mod:item_name` e.g. `minecraft:apple`
// TODO: implement
@@ -16,10 +17,6 @@ enum abstract Item(String) to String {
return new Item(s);
}
function getBase():String {
return this.split(":")[0];
}
var Coal = "minecraft:coal";
var Charcoal = "minecraft:charcoal";
}

View File

@@ -1,24 +0,0 @@
package lib;
class ObjMerge {
public static function merge<T>(obj1:T, obj2:T):T {
if (obj1 == null) {
return obj2;
}
if (obj2 == null) {
return obj1;
}
var rtn:T = Reflect.copy(obj1);
var fields = Reflect.fields(obj2);
for (field in fields) {
if (Reflect.getProperty(obj1, field) == null) {
Reflect.setProperty(rtn, field, Reflect.getProperty(obj2, field));
}
}
return (rtn : T);
}
}

18
src/lib/PaN.hx Normal file
View File

@@ -0,0 +1,18 @@
package lib;
/**
Provider and name. e.g. "minecraft:dirt", "mod:item", "minecraft:overworld"
**/
abstract PaN(String) to String from String {
public inline function new(pan:String) {
this = pan;
}
public function getProvider():String {
return this.split(":")[0];
}
public function getName():String {
return this.split(":")[1];
}
}

View File

@@ -1,46 +0,0 @@
package lib;
import lib.Vec.Vec2;
/**
Reporesents a Point in a 2D `Int` System.
Basicly a wrapper for Vec2<Int> with some extra functions.
**/
@:forward(x, y)
abstract Pos(Vec2<Int>) from Vec2<Int> to Vec2<Int> {
inline public function new(i:Vec2<Int>) {
this = i;
}
@:op(A + B)
public function add(rhs:Vec2<Int>):Pos {
return new Pos({
y: this.y + rhs.y,
x: this.x + rhs.x,
});
}
@:op(A - B)
public function sub(rhs:Vec2<Int>):Pos {
return new Pos({
y: this.y - rhs.y,
x: this.x - rhs.x,
});
}
@:op(A * B)
public function multiply(rhs:Vec2<Int>):Pos {
return new Pos({
y: this.y * rhs.y,
x: this.x * rhs.x,
});
}
@:op(-A)
public function negate():Pos {
return new Pos({
y: -this.y,
x: -this.x,
});
}
}

View File

@@ -1,111 +0,0 @@
package lib;
import lib.Vec.Vec3;
/**
Reporesents a Point in a 3D `Float` System.
Basicly a wrapper for Vec3<Float> with some extra functions.
`Y` represents the height of the point.
**/
@:forward(x, y, z)
abstract Pos3(Vec3<Float>) from Vec3<Float> to Vec3<Float> {
inline public function new(i:Vec3<Float>) {
this = i;
}
@:op(A + B)
public function add(rhs:Vec3<Float>):Pos3 {
return new Pos3({
y: this.y + rhs.y,
x: this.x + rhs.x,
z: this.z + rhs.z
});
}
@:op(A - B)
public function sub(rhs:Vec3<Float>):Pos3 {
return new Pos3({
y: this.y - rhs.y,
x: this.x - rhs.x,
z: this.z - rhs.z
});
}
@:op(A * B)
public function multiplyScalar(rhs:Float):Pos3 {
return new Pos3({
y: this.y * rhs,
x: this.x * rhs,
z: this.z * rhs
});
}
@:op(A / B)
public function divideScalar(rhs:Float):Pos3 {
return new Pos3({
y: this.y / rhs,
x: this.x / rhs,
z: this.z / rhs
});
}
@:op(-A)
public function negate():Pos3 {
return new Pos3({
y: -this.y,
x: -this.x,
z: -this.z
});
}
public function dot(rhs:Vec3<Float>):Float {
return this.x * rhs.x + this.y * rhs.y + this.z * rhs.z;
}
public function cross(rhs:Vec3<Float>):Pos3 {
return new Pos3({
x: this.y * rhs.z - this.z * rhs.y,
y: this.z * rhs.x - this.x * rhs.z,
z: this.x * rhs.y - this.y * rhs.x
});
}
public function normalize():Pos3 {
var l = length();
return multiplyScalar(1 / l);
}
public function length():Float {
return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
}
@:op(A == B)
public function equals(rhs:Pos3):Bool {
return close(rhs);
}
@:op(A != B)
public function notEquals(rhs:Pos3):Bool {
return !close(rhs);
}
public function close(rhs:Pos3, epsilon:Float = 0.001):Bool {
return Math.abs(this.x - rhs.x) < epsilon && Math.abs(this.y - rhs.y) < epsilon && Math.abs(this.z - rhs.z) < epsilon;
}
public function toString():String {
return 'Pos3(${this.x}, ${this.y}, ${this.z})';
}
public function round():Pos3 {
return new Pos3({
x: Math.fround(this.x),
y: Math.fround(this.y),
z: Math.fround(this.z)
});
}
public function distance(rhs:Pos3):Float {
return Math.sqrt(Math.pow(this.x - rhs.x, 2) + Math.pow(this.y - rhs.y, 2) + Math.pow(this.z - rhs.z, 2));
}
}

69
src/lib/PriorityQueue.hx Normal file
View File

@@ -0,0 +1,69 @@
package lib;
using Lambda;
abstract PriorityQueue<T>(Array<{prio:Float, val:T}>) {
public inline function new() {
this = [];
}
public function containsElement(element:T):Bool {
return this.exists((e) -> e.val == element);
}
public function isEmpty():Bool {
return this.length == 0;
}
public function insert(e:T, prio:Float) {
this.push({prio: prio, val: e});
bubbleUp(this.length - 1);
}
public function extractMin():Null<T> {
if (this.length == 0) {
return null;
}
swap(0, this.length - 1);
var minElement = this.pop();
bubbleDown(0);
return minElement.val;
}
private function bubbleUp(index:Int) {
var parentIndex:Int = Math.floor((index - 1) / 2);
if (index > 0 && this[index].prio < this[parentIndex].prio) {
swap(index, parentIndex);
bubbleUp(parentIndex);
}
}
private function bubbleDown(index:Int) {
var leftIndex = 2 * index + 1;
var rightIndex = 2 * index + 2;
var smallest = index;
if (leftIndex < this.length && this[leftIndex].prio < this[smallest].prio) {
smallest = leftIndex;
}
if (rightIndex < this.length && this[rightIndex].prio < this[smallest].prio) {
smallest = rightIndex;
}
if (smallest != index) {
swap(index, smallest);
bubbleDown(smallest);
}
}
private function swap(i:Int, j:Int) {
var tmp = this[i];
this[i] = this[j];
this[j] = tmp;
}
}

View File

@@ -1,10 +1,12 @@
package lib;
class Rect {
private final tl:Pos;
private final br:Pos;
import kernel.peripherals.Screen;
public function new(p1:Pos, p2:Pos) {
class Rect {
public final tl:ScreenPos;
public final br:ScreenPos;
public function new(p1:ScreenPos, p2:ScreenPos) {
this.tl = {
x: MathI.min(p1.x, p2.x),
y: MathI.min(p1.y, p2.y)
@@ -20,11 +22,11 @@ class Rect {
return getWidth() * getHight();
}
public function isInside(p:Pos):Bool {
public function isInside(p:ScreenPos):Bool {
return (p.x >= tl.x && p.x <= br.x) && (p.y >= tl.y && p.y <= br.y);
}
public function isOutside(p:Pos):Bool {
public function isOutside(p:ScreenPos):Bool {
return !this.isInside(p);
}
@@ -36,7 +38,7 @@ class Rect {
return br.x - tl.x;
}
public function offset(pos:Pos) {
public function offset(pos:ScreenPos) {
tl.x += pos.x;
tl.y += pos.y;
br.x += pos.x;

34
src/lib/ScreenPos.hx Normal file
View File

@@ -0,0 +1,34 @@
package lib;
import lib.Vec.Vec2;
/**
Reporesents a Point in a 2D `Int` System.
Basicly a wrapper for Vec2<Int> with some extra functions.
**/
@:forward(x, y)
abstract ScreenPos(Vec2<Int>) from Vec2<Int> to Vec2<Int> {
inline public function new(i:Vec2<Int>) {
this = i;
}
@:op(A + B)
public inline function add(rhs:Vec2<Int>):ScreenPos {
return this.add(rhs);
}
@:op(A - B)
public inline function sub(rhs:Vec2<Int>):ScreenPos {
return this.sub(rhs);
}
@:op(A * B)
public inline function multiply(rhs:Vec2<Int>):ScreenPos {
return this.multiply(rhs);
}
@:op(-A)
public inline function negate():ScreenPos {
return this.negate();
}
}

55
src/lib/SinglePromise.hx Normal file
View File

@@ -0,0 +1,55 @@
package lib;
import kernel.EndOfLoop;
using tink.CoreApi;
class SinglePromise<T> {
private var inProgress:Bool = false;
private var interalPromise:Promise<T> = null;
private var interalResolve:T->Void = null;
private var interalReject:(Error->Void) = null;
private final activate:Void->Void;
public function new(activate:Void->Void) {
this.activate = activate;
}
public function request():Promise<T> {
if (this.inProgress) {
trace("Is progress");
return this.interalPromise;
}
this.inProgress = true;
this.interalPromise = new Promise((resolve, reject) -> {
this.interalResolve = resolve;
this.interalReject = reject;
this.activate();
return null;
});
return this.interalPromise;
}
public function isRunning():Bool {
return this.inProgress;
}
public function resolve(val:T) {
if (this.inProgress) {
this.inProgress = false;
this.interalResolve(val);
}
}
public function reject(err:Error) {
if (this.inProgress) {
this.inProgress = false;
this.interalReject(err);
}
}
}

View File

@@ -0,0 +1,64 @@
package lib;
import kernel.Timer;
using tink.CoreApi;
class SingleTimeoutPromise<T> {
private var inProgress:Bool = false;
private var interalPromise:Promise<T> = null;
private var interalResolve:T->Void = null;
private var interalReject:(Error->Void) = null;
private var timer:Timer = null;
private final activate:Void->Void;
private final timeout:Int;
public function new(timeout:Int, activate:Void->Void) {
this.activate = activate;
this.timeout = timeout;
}
public function request():Promise<T> {
if (this.inProgress) {
return this.interalPromise;
}
this.inProgress = true;
this.interalPromise = new Promise((resolve, reject) -> {
this.interalResolve = resolve;
this.interalReject = reject;
this.activate();
this.timer = new Timer(this.timeout, () -> {
this.reject(new Error("Timeout"));
});
return null;
});
return this.interalPromise;
}
public function isRunning():Bool {
return this.inProgress;
}
public function resolve(val:T) {
if (this.inProgress) {
this.inProgress = false;
this.timer.cancle();
this.interalResolve(val);
}
}
public function reject(err:Error) {
if (this.inProgress) {
this.inProgress = false;
this.timer.cancle();
this.interalReject(err);
}
}
}

49
src/lib/Tags.hx Normal file
View File

@@ -0,0 +1,49 @@
package lib;
import lib.PaN;
using Lambda;
using lua.Table;
/**
Represents a list a minecraft tags.
Needed because tags are SOMETIMES retuned not as arrays.
**/
@:forward
enum abstract Tags(Array<PaN>) from Array<PaN> to Array<PaN> {
inline public function new(arr:Array<String>) {
this = arr;
}
/**
From values that are object with the tag as the filed and the value set to true.
**/
@:from
public static function fromBoolTable(from:Table<String, Bool>) {
var rtn = [];
for (k => _ in Table.toMap(from)) {
rtn.push(k);
}
return new Tags(rtn);
}
/**
From values that are lua arrays.
**/
@:from
public static function fromIntTable(from:Table<Int, String>) {
return new Tags(from.toArray());
}
public function sortByName():Tags {
var copy = this.copy();
copy.sort((a:String, b:String) -> {
return if (a < b) -1 else 1;
});
return copy;
}
}

View File

@@ -1,6 +1,6 @@
package lib;
@:structInit class Vec2<T> {
@:structInit class Vec2<T:Float> {
public var x:T;
public var y:T;
@@ -8,16 +8,96 @@ package lib;
this.x = x;
this.y = y;
}
public function add(rhs:Vec2<T>):Vec2<T> {
return {
y: this.y + rhs.y,
x: this.x + rhs.x,
};
}
public function sub(rhs:Vec2<T>):Vec2<T> {
return {
y: this.y - rhs.y,
x: this.x - rhs.x,
};
}
public function multiply(rhs:Vec2<T>):Vec2<T> {
return {
y: this.y * rhs.y,
x: this.x * rhs.x,
};
}
public function negate():Vec2<T> {
return {
y: -this.y,
x: -this.x,
};
}
}
@:structInit class Vec3<T> {
public var x:T;
public var y:T;
public var z:T;
@:structInit class Vec3<T:Float> {
public final x:T;
public final y:T;
public final z:T;
public function new(x:T, y:T, z:T) {
this.x = x;
this.y = y;
this.z = z;
}
public function add(rhs:Vec3<T>):Vec3<T> {
return {
x: this.x + rhs.x,
y: this.y + rhs.y,
z: this.z + rhs.z
};
}
public function sub(rhs:Vec3<T>):Vec3<T> {
return {
x: this.x - rhs.x,
y: this.y - rhs.y,
z: this.z - rhs.z
};
}
public function multiplyScalar(rhs:T):Vec3<T> {
return {
x: this.x * rhs,
y: this.y * rhs,
z: this.z * rhs
};
}
public function negate():Vec3<T> {
return {
x: -this.x,
y: -this.y,
z: -this.z
};
}
public function dot(rhs:Vec3<T>):Float {
return this.x * rhs.x + this.y * rhs.y + this.z * rhs.z;
}
public function cross(rhs:Vec3<T>):Vec3<T> {
return {
x: this.y * rhs.z - this.z * rhs.y,
y: this.z * rhs.x - this.x * rhs.z,
z: this.x * rhs.y - this.y * rhs.x
};
}
public function length():Float {
return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
}
public function distance(rhs:Vec3<T>):Float {
return Math.sqrt(Math.pow(this.x - rhs.x, 2) + Math.pow(this.y - rhs.y, 2) + Math.pow(this.z - rhs.z, 2));
}
}

86
src/lib/WorldPos.hx Normal file
View File

@@ -0,0 +1,86 @@
package lib;
import lib.Vec.Vec2;
import lib.Vec.Vec3;
/**
Reporesents a Point in a 3D `Float` System.
Basicly a wrapper for Vec3<Float> with some extra functions.
`Y` represents the height of the point.
**/
@:forward
abstract WorldPos(Vec3<Float>) from Vec3<Float> to Vec3<Float> {
public inline function new(v:Vec3<Float>) {
this = v;
}
@:op(A + B)
public inline function add(rhs:WorldPos):WorldPos {
return this.add(rhs);
}
@:op(A - B)
public inline function sub(rhs:Vec3<Float>):WorldPos {
return this.sub(rhs);
}
@:op(A * B)
public inline function multiplyScalar(rhs:Float):WorldPos {
return this.multiplyScalar(rhs);
}
@:op(A / B)
public function divideScalar(rhs:Float):WorldPos {
return {
y: this.y / rhs,
x: this.x / rhs,
z: this.z / rhs
};
}
@:op(-A)
public function negate():WorldPos {
return this.negate();
}
public function normalize():WorldPos {
var l = this.length();
return multiplyScalar(1 / l);
}
@:op(A == B)
public function equals(rhs:WorldPos):Bool {
return close(rhs);
}
@:op(A != B)
public function notEquals(rhs:WorldPos):Bool {
return !close(rhs);
}
public function close(rhs:WorldPos, epsilon:Float = 0.001):Bool {
return Math.abs(this.x - rhs.x) < epsilon && Math.abs(this.y - rhs.y) < epsilon && Math.abs(this.z - rhs.z) < epsilon;
}
public function toString():String {
return 'WorldPos(${this.x}, ${this.y}, ${this.z})';
}
/**
Returns the chunk the position is in.
**/
public function chunk():Vec2<Int> {
var x = Math.floor(this.x / 16);
var z = Math.floor(this.z / 16);
return new Vec2(x, z);
}
public function round():WorldPos {
return new WorldPos({
x: Math.fround(this.x),
y: Math.fround(this.y),
z: Math.fround(this.z)
});
}
}

View File

@@ -1,27 +0,0 @@
package lib.observable;
using tink.CoreApi;
class DummyObservable<T> implements IObservable<T> {
private var value:T;
private function new(value:T) {
this.value = value;
}
public function set(value:T) {
throw new haxe.exceptions.NotImplementedException();
}
public function get():T {
return this.value;
}
public function subscribe(callback:Callback<T>):CallbackLink {
return null;
}
public static function dummy<T>(value:T):IObservable<T> {
return new DummyObservable<T>(value);
}
}

View File

@@ -1,9 +0,0 @@
package lib.observable;
using tink.CoreApi;
interface IObservable<T> {
public function set(value:T):Void;
public function get():T;
public function subscribe(callback:Callback<T>):CallbackLink;
}

View File

@@ -1,56 +0,0 @@
package lib.observable;
class ObservableArray<T> extends ObservableValue<Array<T>> {
public function new(value:Array<T>) {
super(value);
}
public function insert(pos:Int, x:T):Void {
this.value.insert(pos, x);
this.callbacks.invoke(this.value);
}
public function pop():Null<T> {
var poped = this.pop();
this.callbacks.invoke(this.value);
return poped;
}
public function push(x:T):Int {
var i = this.value.push(x);
this.callbacks.invoke(this.value);
return i;
}
public function remove(x:T):Bool {
var b = this.value.remove(x);
this.callbacks.invoke(this.value);
return b;
}
public function resize(len:Int) {
this.value.resize(len);
this.callbacks.invoke(this.value);
}
public function reverse() {
this.value.reverse();
this.callbacks.invoke(this.value);
}
public function shift():Null<T> {
var e = this.value.shift();
this.callbacks.invoke(this.value);
return e;
}
public function sort(f:(T, T) -> Int):Void {
this.value.sort(f);
this.callbacks.invoke(this.value);
}
public function unshift(x:T):Void {
this.value.unshift(x);
this.callbacks.invoke(this.value);
}
}

View File

@@ -1,28 +0,0 @@
package lib.observable;
using tink.CoreApi;
class ObservableValue<T> implements IObservable<T> {
private var value:T;
private var callbacks:CallbackList<T> = new CallbackList();
public function new(value:T) {
this.value = value;
}
public function set(value:T) {
if (value != this.value) {
this.value = value;
callbacks.invoke(value);
}
}
public function get():T {
return value;
}
public function subscribe(callback:Callback<T>):CallbackLink {
callback.invoke(value);
return callbacks.add(callback);
}
}

View File

@@ -14,4 +14,16 @@ class Helper {
return Success(null);
}
public static function combine(steps:Array<Void->Outcome<Noise, String>>):Outcome<Noise, String> {
for (step in steps) {
var res = step();
if (!res.isSuccess()) {
return res;
}
}
return Success(null);
}
}

View File

@@ -0,0 +1,15 @@
package lib.turtle.planner;
import kernel.turtle.Types.InteractDirections;
enum Action {
Forward;
Back;
Up;
Down;
TurnLeft;
TurnRight;
Clear(dir:InteractDirections); // Digs in the direction. Does not fail if nothing to dig. Trys again if still blocked.
FullTunnel; // Digs front, move forward, dig down. Like a tunnel for a player.
HalfTunnel; // Just clears s 1x2 tunnel infront of it. Like FullTunnel but without moving.
}

View File

@@ -0,0 +1,41 @@
package lib.turtle.planner;
import kernel.turtle.Turtle;
using tink.CoreApi;
class Executer {
public function new() {}
public function execute(action:Action) {
var r = translate(action);
switch r {
case Failure(failure):
throw new Error(failure); // I think that this a valid place to use exeptions.
default:
}
}
private function translate(action:Action):Outcome<Noise, String> {
switch (action) {
case Forward:
return Turtle.forward();
case Back:
return Turtle.back();
case Up:
return Turtle.up();
case Down:
return Turtle.down();
case TurnLeft:
return Turtle.turnLeft();
case TurnRight:
return Turtle.turnRight();
case Clear(dir):
return Turtle.digEmpty(dir);
case FullTunnel:
return Helper.combine([Turtle.digEmpty.bind(Front), Turtle.forward, Turtle.digEmpty.bind(Down)]);
case HalfTunnel:
return Helper.combine([Turtle.digEmpty.bind(Front), Turtle.down, Turtle.digEmpty.bind(Front), Turtle.up]);
}
}
}

View File

@@ -0,0 +1,142 @@
package lib.turtle.planner;
import kernel.turtle.TurtleMutex;
import kernel.ps.ProcessHandle;
using tink.CoreApi;
/**
A plan repesents a pice of a squence of turtle commands.
It acts like node in a linked list.
It also has no interal state and can be reused.
Once a plan has been setup it (should be) is immutable.
**/
class Plan {
private var prev:Null<Plan>;
private var delegate:(Executer) -> Void;
public static function newPlan():Plan {
return new Plan();
}
private function new() {}
/**
Do a sequence of actions
**/
public function act(actions:Array<Action>):Plan {
#if debug
if (this.delegate != null) {
throw new Error("Can't reuse plans");
}
#end
this.delegate = (ex) -> {
trace('Act: $actions');
for (a in actions) {
ex.execute(a);
}
};
var next = new Plan();
next.prev = this;
return next;
}
/**
Do a sequence of actions multiple times.
**/
public function repeat(actions:Array<Action>, times:Int):Plan {
#if debug
if (this.delegate != null) {
throw new Error("Can't reuse plans");
}
#end
this.delegate = (ex) -> {
trace('Rep: $actions');
for (i in 0...times) {
for (a in actions) {
ex.execute(a);
}
}
};
var next = new Plan();
next.prev = this;
return next;
}
/**
Append a plan to the current chain.
**/
public function subplan(plan:Plan):Plan {
#if debug
if (this.delegate != null) {
throw new Error("Can't reuse plans");
}
#end
this.delegate = (ex) -> {
trace('Subplan');
plan.execute(ex);
};
var next = new Plan();
next.prev = this;
return next;
}
/**
Execute a plan mutiple times.
**/
public function repateSubplan(plan:Plan, times:Int):Plan {
#if debug
if (this.delegate != null) {
throw new Error("Can't reuse plans");
}
#end
this.delegate = (ex) -> {
trace('Subplan repeat');
for (_ in 0...times) {
plan.execute(ex);
}
};
var next = new Plan();
next.prev = this;
return next;
}
private function execute(ex:Executer) {
if (this.prev == null) {
this.delegate(ex);
return;
}
this.prev.execute(ex);
if (this.delegate != null) { // The last plan in a line does not have a delegate
this.delegate(ex);
}
return;
}
public function begin(handle:ProcessHandle) {
trace("begin plan");
if (!handle.claimTurtleMutex()) {
handle.writeLine("Failed to claim turtle mutex");
handle.close();
return;
}
var ex = new Executer();
TurtleMutex.runInTThread(() -> {
try {
this.execute(ex);
} catch (e) {
handle.writeLine(e.message);
}
handle.close(true);
});
}
}

View File

@@ -1,7 +1,7 @@
package lib.ui;
import kernel.ui.WindowContext;
import lib.Pos;
import lib.ScreenPos;
import lib.Rect;
import kernel.ui.Pixel;
@@ -10,7 +10,7 @@ abstract Canvas(Array<Array<Pixel>>) to Array<Array<Pixel>> from Array<Array<Pix
this = [[]];
}
public inline function set(i:Pos, pixel:Pixel) {
public inline function set(i:ScreenPos, pixel:Pixel) {
if (this[i.y] == null) {
this[i.y] = [];
}
@@ -18,7 +18,7 @@ abstract Canvas(Array<Array<Pixel>>) to Array<Array<Pixel>> from Array<Array<Pix
this[i.y][i.x] = pixel;
}
public inline function get(i:Pos):Null<Pixel> {
public inline function get(i:ScreenPos):Null<Pixel> {
if (this[i.y] == null) {
return null;
}
@@ -26,11 +26,11 @@ abstract Canvas(Array<Array<Pixel>>) to Array<Array<Pixel>> from Array<Array<Pix
return this[i.y][i.x];
}
public function keyValueIterator():KeyValueIterator<Pos, Pixel> {
public function keyValueIterator():KeyValueIterator<ScreenPos, Pixel> {
return new CanvasKeyValueIterator(this);
}
public function combine(other:Canvas, offset:Pos) {
public function combine(other:Canvas, offset:ScreenPos) {
for (key => value in other) {
if (value == null) {
continue;
@@ -127,8 +127,8 @@ abstract Canvas(Array<Array<Pixel>>) to Array<Array<Pixel>> from Array<Array<Pix
class CanvasKeyValueIterator {
private final canvas:Array<Array<Pixel>>;
private var index:Null<Pos> = {x: 0, y: 0};
private var nextIndex:Null<Pos> = null;
private var index:Null<ScreenPos> = {x: 0, y: 0};
private var nextIndex:Null<ScreenPos> = null;
@:allow(lib.ui.Canvas)
private function new(canvas:Array<Array<Pixel>>) {
@@ -141,7 +141,7 @@ class CanvasKeyValueIterator {
this.nextIndex = nextValidPixel();
}
private function isValidPos(pos:Pos):Bool {
private function isValidPos(pos:ScreenPos):Bool {
if (this.canvas[pos.y] == null) {
return false;
}
@@ -157,7 +157,7 @@ class CanvasKeyValueIterator {
return this.index != null;
}
private function nextValidPixel():Null<Pos> {
private function nextValidPixel():Null<ScreenPos> {
if (this.index == null) {
return null;
}
@@ -182,7 +182,7 @@ class CanvasKeyValueIterator {
return null;
}
public function next():{key:Pos, value:Pixel} {
public function next():{key:ScreenPos, value:Pixel} {
var rtn = {
key: this.index,
value: this.canvas[this.index.y][this.index.x]

View File

@@ -1,17 +1,17 @@
package lib.ui;
import lib.Pos;
import lib.ScreenPos;
import kernel.ButtonType;
using tink.CoreApi;
typedef UIEvents = {
public var ?onClick:Callback<{button:ButtonType, pos:Pos}>;
public var ?onClick:Callback<{button:ButtonType, pos:ScreenPos}>;
public var ?onKey:Callback<{keyCode:Int, isHeld:Bool}>;
public var ?onKeyUp:Callback<Int>;
public var ?onMouseDrag:Callback<{button:ButtonType, pos:Pos}>;
public var ?onMouseScroll:Callback<{dir:Int, pos:Pos}>;
public var ?onMouseUp:Callback<{button:ButtonType, pos:Pos}>;
public var ?onMouseDrag:Callback<{button:ButtonType, pos:ScreenPos}>;
public var ?onMouseScroll:Callback<{dir:Int, pos:ScreenPos}>;
public var ?onMouseUp:Callback<{button:ButtonType, pos:ScreenPos}>;
public var ?onPaste:Callback<String>;
public var ?onChar:Callback<String>;
}

View File

@@ -15,7 +15,7 @@ abstract EventMap(Array<{bound:Rect, delegate:IUIEventDelegate}>) {
}
}
public function findResponseableDelegate(pos:Pos):IUIEventDelegate {
public function findResponseableDelegate(pos:ScreenPos):IUIEventDelegate {
for (i in 0...this.length) {
var newi = (this.length - 1) - i;
var element = this[newi];

View File

@@ -3,5 +3,5 @@ package lib.ui.elements;
import lib.ui.rendere.IUIEventDelegate;
interface IUIElement extends IUIEventDelegate {
public function render(bounds:Pos):Canvas;
public function render(bounds:ScreenPos):Canvas;
}

View File

@@ -1,7 +1,7 @@
package lib.ui.elements;
class RootElement implements IUIElement {
private var children:Array<IUIElement>;
private var children:Array<IUIElement> = [];
private final eventManager:UIEventManager = new UIEventManager();
private var title:String = "";
@@ -21,15 +21,15 @@ class RootElement implements IUIElement {
this.children = children;
}
public function render(bounds:Pos):Canvas {
public function render(bounds:ScreenPos):Canvas {
var canvas = new Canvas();
var offset = new Pos({x: 0, y: 0});
var offset = new ScreenPos({x: 0, y: 0});
if (hasTitle()) {
var title = new TextElement(this.title);
var halfWidth = Math.floor(bounds.x / 2) - Math.floor(this.title.length / 2);
canvas.combine(title.render(bounds), {x: halfWidth, y: offset.y});
offset = new Pos({x: 0, y: 1});
offset = new ScreenPos({x: 0, y: 1});
}
this.eventManager.clearMap();
@@ -42,7 +42,7 @@ class RootElement implements IUIElement {
this.eventManager.addMapElement(child, bounds);
canvas.combine(childCanvas, offset);
offset = new Pos({x: 0, y: offset.y + bounds.getHight() + 1});
offset = new ScreenPos({x: 0, y: offset.y + bounds.getHight() + 1});
}
return canvas;

View File

@@ -26,7 +26,7 @@ class TextElement implements IUIElement {
return uiEvents;
}
public function render(bounds:Pos):Canvas {
public function render(bounds:ScreenPos):Canvas {
var canvas = new Canvas();
var x = 0;

View File

@@ -30,7 +30,7 @@ class UIEventManager implements IUIEventDelegate {
};
}
private function handleClickEvent(params:{button:ButtonType, pos:Pos}) {
private function handleClickEvent(params:{button:ButtonType, pos:ScreenPos}) {
var element = this.map.findResponseableDelegate(params.pos);
if (element == null) {
return;
@@ -48,11 +48,11 @@ class UIEventManager implements IUIEventDelegate {
private function handlePasteEvent(text:String) {}
private function handleMouseUpEvent(params:{button:ButtonType, pos:Pos}) {}
private function handleMouseUpEvent(params:{button:ButtonType, pos:ScreenPos}) {}
private function handleMouseScrollEvent(params:{dir:Int, pos:Pos}) {}
private function handleMouseScrollEvent(params:{dir:Int, pos:ScreenPos}) {}
private function handleMouseDragEvent(params:{button:ButtonType, pos:Pos}) {}
private function handleMouseDragEvent(params:{button:ButtonType, pos:ScreenPos}) {}
private function handleKeyEvent(params:{keyCode:Int, isHeld:Bool}) {}

View File

@@ -6,6 +6,41 @@ function time() {
return `${now.getHours().toString().padStart(2, "0")}:${now.getMinutes().toString().padStart(2, 0)}:${now.getSeconds().toString().padStart(2, 0)}`;
}
const Reset = "\x1b[0m";
const FgBlack = "\x1b[30m"
const FgRed = "\x1b[31m"
const FgGreen = "\x1b[32m"
const FgYellow = "\x1b[33m"
const FgBlue = "\x1b[34m"
const FgMagenta = "\x1b[35m"
const FgCyan = "\x1b[36m"
const FgWhite = "\x1b[37m"
const FgGray = "\x1b[90m"
function log(id, lvl, msg) {
var logLvl = "UNKN";
switch (lvl) {
case "0":
logLvl = "INFO";
break;
case "1":
logLvl = FgYellow + "WARN" + Reset;
break;
case "2":
logLvl = FgRed + "ERRO" + Reset;
break;
case "3":
logLvl = FgGray + "DEBG" + Reset;
break;
case "4":
logLvl = FgWhite + "SILY" + Reset;
break;
}
console.log(`(${id})[${logLvl}]${msg}`);
}
const server = http.createServer((req, res) => {
if (req.method != "POST") {
@@ -13,7 +48,9 @@ const server = http.createServer((req, res) => {
return;
}
var id = req.url.substring(1);
var urlParts = req.url.split("/");
var id = urlParts[2];
var lvl = urlParts[3];
let data = "";
@@ -22,11 +59,11 @@ const server = http.createServer((req, res) => {
})
req.on('end', () => {
console.log(`[${time()}][${id}]${data}`);
log(id, lvl, data);
res.writeHead(200);
res.end();
})
});
console.log("Listening on port 8080")
console.log("Webconsole running on port 8080");
server.listen(8080);

View File

@@ -1,15 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
luamin@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/luamin/-/luamin-1.0.4.tgz#944529b58fc6fa4d31eace2e0353d41210f0e3d3"
integrity sha1-lEUptY/G+k0x6s4uA1PUEhDw49M=
dependencies:
luaparse "^0.2.1"
luaparse@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/luaparse/-/luaparse-0.2.1.tgz#aa8f56132b0de97d37f3c991a9df42e0e17f656c"
integrity sha1-qo9WEysN6X0388mRqd9C4OF/ZWw=