package lib.turtle; import kernel.log.Log; import kernel.turtle.Types.TurtleSlot; import kernel.turtle.Types.InteractDirections; import kernel.turtle.Turtle; using Lambda; using tink.CoreApi; typedef InvState = Map; /** A wrapper for the native turtle inventory functions. Adds usefullt helper functions. **/ class InvManager { public function new() {} private static function getInvState() { var invState:InvState = new Map(); for (i in 0...Turtle.MAX_SLOTS) { var detail = Turtle.getItemDetail(i); var spaceLeft = Turtle.getItemSpace(i); switch detail { case Some(v): invState.set(i, { count: v.count, name: v.name, max: spaceLeft + v.count }); case None: invState.remove(i); } } return invState; } public static function getItemCountInfo():Map { var invState = getInvState(); var rtn:Map = new Map(); for (slot in invState) { if (!rtn.exists(slot.name)) { rtn.set(slot.name, slot.count); continue; } var count = rtn.get(slot.name); rtn.set(slot.name, count + slot.count); } return rtn; } private static function getSlotWithMinCountForItem(item:Item, invState:InvState):Null { var min:Int = 99; // TODO: is there something like MAX_INT ??? var minSlot:TurtleSlot = -1; for (k => slot in invState) { if (slot.name != item) { continue; } if (slot.count < min) { min = slot.count; minSlot = k; } }; return minSlot == -1 ? null : minSlot; } public static function place(item:Item, dir:InteractDirections):Outcome { var invState = getInvState(); var slot = getSlotWithMinCountForItem(item, invState); if (slot == null) { return Failure("Item not in inventory"); } return placeSlot(slot, dir); } private static function placeSlot(slot:TurtleSlot, dir:InteractDirections):Outcome { Turtle.selectSlot(slot); // TODO: handle error return Turtle.place(dir); } private static function getSlotsForItems(invState:InvState):Map> { var rtn:Map> = new Map(); for (k => slot in invState) { if (!rtn.exists(slot.name)) { rtn.set(slot.name, [k]); continue; } var value = rtn.get(slot.name); value.push(k); rtn.set(slot.name, value); } return rtn; } /** Cleans up turtle inventory. Moves items together. This should be run in a turtle thread. **/ public static function defrag() { var invState = getInvState(); var items = getSlotsForItems(invState); for (item in items) { defragItem(invState, item); } } private static function defragItem(invState:InvState, slots:Array) { // Sort slots by items inside. slots.sort((a, b) -> { return invState.get(a).count - invState.get(b).count; }); var maxInSlot = invState.get(slots[0]).max; // Loop from both sides. Move the slots with the lowest count into the fullest. var topIndex = slots.length - 1; for (k => slot in slots) { if (topIndex < k) { return; } var count = invState.get(slot).count; if (count == maxInSlot) { return; } var loopProtect = 20; while (loopProtect > 0) { loopProtect--; if (topIndex < k) { return; } var topCount = invState.get(slots[topIndex]).count; var spaceInTop = maxInSlot - topCount; if (spaceInTop == 0) { topIndex--; continue; } var transferCount = MathI.min(count, spaceInTop); if (!Turtle.transfer(slot, slots[topIndex], transferCount).isSuccess()) { Log.error("Failed to transfer items"); } var topState = invState.get(slots[topIndex]); topState.count += transferCount; invState.set(slots[topIndex], topState); var botState = invState.get(slot); botState.count -= transferCount; invState.set(slot, botState); count -= transferCount; if (count <= 0) { break; } } if (loopProtect < 0) { Log.silly("Loopprotection triggerd"); } } } }