mirror of
				https://github.com/SikongJueluo/cc-utils.git
				synced 2025-11-04 19:27:50 +08:00 
			
		
		
		
	fix: accesscontrol toast; feature: autocraft basic; reconstruct: autocraft
fix: - accesscontrol send toast failed - advanced peripherals BlockDetailData wrong record type feature: - autocraft support multi package craft - autocraft more fast craft speed reconstruct: - CraftManager algorithm - autocraft logic
This commit is contained in:
		@@ -124,9 +124,9 @@ function sendNotice(player: string, playerInfo?: PlayerInfo) {
 | 
			
		||||
  const noticeTargetPlayers = config.adminGroupConfig.groupUsers.concat(
 | 
			
		||||
    config.usersGroups
 | 
			
		||||
      .filter((value) => value.isNotice)
 | 
			
		||||
      .map((value) => value.groupUsers ?? [])
 | 
			
		||||
      .flat(),
 | 
			
		||||
      .flatMap((value) => value.groupUsers ?? []),
 | 
			
		||||
  );
 | 
			
		||||
  logger.debug(`noticeTargetPlayers: ${noticeTargetPlayers.join(", ")}`);
 | 
			
		||||
 | 
			
		||||
  for (const targetPlayer of noticeTargetPlayers) {
 | 
			
		||||
    if (!onlinePlayers.includes(targetPlayer)) continue;
 | 
			
		||||
@@ -134,6 +134,7 @@ function sendNotice(player: string, playerInfo?: PlayerInfo) {
 | 
			
		||||
      name: player,
 | 
			
		||||
      info: playerInfo,
 | 
			
		||||
    });
 | 
			
		||||
    sleep(1);
 | 
			
		||||
  }
 | 
			
		||||
  releaser.release();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,22 +1,30 @@
 | 
			
		||||
import { CraftManager } from "@/lib/CraftManager";
 | 
			
		||||
import { CCLog } from "@/lib/ccLog";
 | 
			
		||||
import {
 | 
			
		||||
  CraftManager,
 | 
			
		||||
  CraftRecipe,
 | 
			
		||||
  CreatePackageTag,
 | 
			
		||||
} from "@/lib/CraftManager";
 | 
			
		||||
import { CCLog, LogLevel } from "@/lib/ccLog";
 | 
			
		||||
import { Queue } from "@/lib/datatype/Queue";
 | 
			
		||||
 | 
			
		||||
const logger = new CCLog("autocraft.log");
 | 
			
		||||
const logger = new CCLog("autocraft.log", { outputMinLevel: LogLevel.Info });
 | 
			
		||||
 | 
			
		||||
const peripheralsNames = {
 | 
			
		||||
  packagesContainer: "minecraft:chest_10",
 | 
			
		||||
  itemsContainer: "minecraft:chest_9",
 | 
			
		||||
  packageExtractor: "create:packager_1",
 | 
			
		||||
  blockReader: "front",
 | 
			
		||||
  wiredModem: "back",
 | 
			
		||||
  redstone: "front",
 | 
			
		||||
  // packsInventory: "minecraft:chest_14",
 | 
			
		||||
  // itemsInventory: "minecraft:chest_15",
 | 
			
		||||
  // packageExtractor: "create:packager_3",
 | 
			
		||||
  blockReader: "bottom",
 | 
			
		||||
  wiredModem: "right",
 | 
			
		||||
  redstone: "left",
 | 
			
		||||
  packsInventory: "minecraft:chest_1121",
 | 
			
		||||
  itemsInventory: "minecraft:chest_1120",
 | 
			
		||||
  packageExtractor: "create:packager_0",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const packagesContainer = peripheral.wrap(
 | 
			
		||||
  peripheralsNames.packagesContainer,
 | 
			
		||||
const packsInventory = peripheral.wrap(
 | 
			
		||||
  peripheralsNames.packsInventory,
 | 
			
		||||
) as InventoryPeripheral;
 | 
			
		||||
const itemsContainer = peripheral.wrap(
 | 
			
		||||
  peripheralsNames.itemsContainer,
 | 
			
		||||
const itemsInventory = peripheral.wrap(
 | 
			
		||||
  peripheralsNames.itemsInventory,
 | 
			
		||||
) as InventoryPeripheral;
 | 
			
		||||
const packageExtractor = peripheral.wrap(
 | 
			
		||||
  peripheralsNames.packageExtractor,
 | 
			
		||||
@@ -31,87 +39,170 @@ const turtleLocalName = wiredModem.getNameLocal();
 | 
			
		||||
 | 
			
		||||
enum State {
 | 
			
		||||
  IDLE,
 | 
			
		||||
  CHECK_PACK,
 | 
			
		||||
  READ_RECIPE,
 | 
			
		||||
  PULL_ITEMS,
 | 
			
		||||
  CRAFT_OUTPUT,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function main() {
 | 
			
		||||
  const craftManager = new CraftManager(turtleLocalName);
 | 
			
		||||
  const craftManager = new CraftManager(turtleLocalName, itemsInventory);
 | 
			
		||||
  const recipesQueue = new Queue<CraftRecipe>();
 | 
			
		||||
  const recipesWaitingMap = new Map<number, CraftRecipe[] | CraftRecipe>();
 | 
			
		||||
  let currentState = State.IDLE;
 | 
			
		||||
  let nextState = State.IDLE;
 | 
			
		||||
  let hasPackage = redstone.getInput(peripheralsNames.redstone);
 | 
			
		||||
  // let currentState = State.IDLE;
 | 
			
		||||
  // let nextState = State.IDLE;
 | 
			
		||||
  while (hasPackage) {
 | 
			
		||||
    hasPackage = redstone.getInput(peripheralsNames.redstone);
 | 
			
		||||
    logger.warn("redstone activated when init, please clear inventory");
 | 
			
		||||
    sleep(1);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  logger.info("AutoCraft init finished...");
 | 
			
		||||
  while (true) {
 | 
			
		||||
    if (!hasPackage) os.pullEvent("redstone");
 | 
			
		||||
    hasPackage = redstone.getInput(peripheralsNames.redstone);
 | 
			
		||||
    if (!hasPackage) {
 | 
			
		||||
      continue;
 | 
			
		||||
    // Switch state
 | 
			
		||||
    switch (currentState) {
 | 
			
		||||
      case State.IDLE: {
 | 
			
		||||
        nextState = hasPackage ? State.READ_RECIPE : State.IDLE;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      case State.READ_RECIPE: {
 | 
			
		||||
        nextState = hasPackage ? State.READ_RECIPE : State.CRAFT_OUTPUT;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      case State.CRAFT_OUTPUT: {
 | 
			
		||||
        nextState =
 | 
			
		||||
          recipesQueue.size() > 0
 | 
			
		||||
            ? State.CRAFT_OUTPUT
 | 
			
		||||
            : hasPackage
 | 
			
		||||
              ? State.READ_RECIPE
 | 
			
		||||
              : State.IDLE;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      default: {
 | 
			
		||||
        logger.error(`Unknown state`);
 | 
			
		||||
        nextState = hasPackage ? State.READ_RECIPE : State.IDLE;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    logger.info(`Package detected`);
 | 
			
		||||
 | 
			
		||||
    const itemsInfo = packagesContainer.list();
 | 
			
		||||
    for (const key in itemsInfo) {
 | 
			
		||||
      const slot = parseInt(key);
 | 
			
		||||
      const item = itemsInfo[slot];
 | 
			
		||||
      logger.info(`${item.count}x ${item.name} in slot ${key}`);
 | 
			
		||||
 | 
			
		||||
      // Get package NBT
 | 
			
		||||
      packagesContainer.pushItems(turtleLocalName, slot);
 | 
			
		||||
      const packageInfo = blockReader.getBlockData()!.Items[1];
 | 
			
		||||
      // log.info(textutils.serialise(packageInfo));
 | 
			
		||||
 | 
			
		||||
      // Get recipe
 | 
			
		||||
      const packageRecipes = CraftManager.getPackageRecipe(packageInfo);
 | 
			
		||||
 | 
			
		||||
      // No recipe, just extract package
 | 
			
		||||
      if (packageRecipes.isNone()) {
 | 
			
		||||
        packageExtractor.pullItems(turtleLocalName, 1);
 | 
			
		||||
        logger.info(`No recipe, just pass`);
 | 
			
		||||
        continue;
 | 
			
		||||
    // State logic
 | 
			
		||||
    switch (currentState) {
 | 
			
		||||
      case State.IDLE: {
 | 
			
		||||
        if (!hasPackage) os.pullEvent("redstone");
 | 
			
		||||
        hasPackage = redstone.getInput(peripheralsNames.redstone);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Extract package
 | 
			
		||||
      // log.info(`Get recipe ${textutils.serialise(recipe)}`);
 | 
			
		||||
      packageExtractor.pullItems(turtleLocalName, 1);
 | 
			
		||||
      case State.READ_RECIPE: {
 | 
			
		||||
        logger.info(`Package detected`);
 | 
			
		||||
        const packagesInfoRecord = packsInventory.list();
 | 
			
		||||
        for (const key in packagesInfoRecord) {
 | 
			
		||||
          const slotNum = parseInt(key);
 | 
			
		||||
          packsInventory.pushItems(turtleLocalName, slotNum);
 | 
			
		||||
 | 
			
		||||
          // Get package NBT
 | 
			
		||||
          logger.debug(
 | 
			
		||||
            `Turtle:\n${textutils.serialise(blockReader.getBlockData()!, { allow_repetitions: true })}`,
 | 
			
		||||
          );
 | 
			
		||||
          const packageDetailInfo = blockReader.getBlockData()?.Items[1];
 | 
			
		||||
          if (packageDetailInfo === undefined) {
 | 
			
		||||
            logger.error(`Package detail info not found`);
 | 
			
		||||
            continue;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          // Get OrderId and isFinal
 | 
			
		||||
          const packageOrderId = (packageDetailInfo.tag as CreatePackageTag)
 | 
			
		||||
            .Fragment.OrderId;
 | 
			
		||||
          const packageIsFinal =
 | 
			
		||||
            (packageDetailInfo.tag as CreatePackageTag).Fragment.IsFinal > 0
 | 
			
		||||
              ? true
 | 
			
		||||
              : false;
 | 
			
		||||
 | 
			
		||||
          // Get recipe
 | 
			
		||||
          const packageRecipes =
 | 
			
		||||
            CraftManager.getPackageRecipe(packageDetailInfo);
 | 
			
		||||
          if (packageRecipes.isSome()) {
 | 
			
		||||
            if (packageIsFinal) recipesQueue.enqueue(packageRecipes.value);
 | 
			
		||||
            else recipesWaitingMap.set(packageOrderId, packageRecipes.value);
 | 
			
		||||
          } else {
 | 
			
		||||
            if (packageIsFinal && recipesWaitingMap.has(packageOrderId)) {
 | 
			
		||||
              recipesQueue.enqueue(recipesWaitingMap.get(packageOrderId)!);
 | 
			
		||||
              recipesWaitingMap.delete(packageOrderId);
 | 
			
		||||
            } else {
 | 
			
		||||
              logger.debug(`No recipe, just pass`);
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          packageExtractor.pullItems(turtleLocalName, 1);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (
 | 
			
		||||
          currentState === State.READ_RECIPE &&
 | 
			
		||||
          nextState === State.CRAFT_OUTPUT
 | 
			
		||||
        ) {
 | 
			
		||||
          craftManager.initItemsMap();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      case State.CRAFT_OUTPUT: {
 | 
			
		||||
        // Check recipe
 | 
			
		||||
        const recipe = recipesQueue.dequeue();
 | 
			
		||||
        if (recipe === undefined) break;
 | 
			
		||||
 | 
			
		||||
      // Pull and craft multi recipe
 | 
			
		||||
      for (const recipe of packageRecipes.value) {
 | 
			
		||||
        let craftOutputItem: BlockItemDetailData | undefined = undefined;
 | 
			
		||||
        let restCraftCnt = recipe.Count;
 | 
			
		||||
        let maxSignleCraftCnt = restCraftCnt;
 | 
			
		||||
 | 
			
		||||
        let craftItemDetail: ItemDetail | undefined = undefined;
 | 
			
		||||
        do {
 | 
			
		||||
          // Clear workbench
 | 
			
		||||
          craftManager.pushAll(itemsContainer);
 | 
			
		||||
          craftManager.clearTurtle();
 | 
			
		||||
 | 
			
		||||
          logger.info(`Pull items according to a recipe`);
 | 
			
		||||
          const craftCnt = craftManager
 | 
			
		||||
            .pullItems(recipe, itemsContainer, restCraftCnt)
 | 
			
		||||
            .pullItemsWithRecipe(recipe, maxSignleCraftCnt)
 | 
			
		||||
            .unwrapOrElse((error) => {
 | 
			
		||||
              logger.error(error.message);
 | 
			
		||||
              return 0;
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
          if (craftCnt == 0) break;
 | 
			
		||||
          craftManager.craft();
 | 
			
		||||
          if (craftCnt < maxSignleCraftCnt) maxSignleCraftCnt = craftCnt;
 | 
			
		||||
          const craftRet = craftManager.craft(maxSignleCraftCnt);
 | 
			
		||||
          craftItemDetail ??= craftRet;
 | 
			
		||||
          logger.info(`Craft ${craftCnt} times`);
 | 
			
		||||
          restCraftCnt -= craftCnt;
 | 
			
		||||
 | 
			
		||||
          // Get output item
 | 
			
		||||
          craftOutputItem ??= blockReader.getBlockData()!.Items[1];
 | 
			
		||||
        } while (restCraftCnt > 0);
 | 
			
		||||
 | 
			
		||||
        // Finally output
 | 
			
		||||
        if (restCraftCnt > 0) {
 | 
			
		||||
          logger.warn(`Only craft ${recipe.Count - restCraftCnt} times`);
 | 
			
		||||
          logger.warn(
 | 
			
		||||
            `Only craft ${recipe.Count - restCraftCnt}x ${craftItemDetail?.name ?? "UnknownItem"}`,
 | 
			
		||||
          );
 | 
			
		||||
        } else {
 | 
			
		||||
          logger.info(`Finish craft ${recipe.Count}x ${craftOutputItem?.id}`);
 | 
			
		||||
          logger.info(
 | 
			
		||||
            `Finish craft ${recipe.Count}x ${craftItemDetail?.name ?? "UnknownItem"}`,
 | 
			
		||||
          );
 | 
			
		||||
        }
 | 
			
		||||
        craftManager.pushAll(itemsContainer);
 | 
			
		||||
 | 
			
		||||
        // Clear workbench and inventory
 | 
			
		||||
        const turtleItemSlots = Object.values(
 | 
			
		||||
          blockReader.getBlockData()!.Items,
 | 
			
		||||
        ).map((val) => val.Slot + 1);
 | 
			
		||||
        craftManager.clearTurtle(turtleItemSlots);
 | 
			
		||||
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      default: {
 | 
			
		||||
        sleep(1);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Check packages
 | 
			
		||||
    hasPackage = redstone.getInput(peripheralsNames.redstone);
 | 
			
		||||
    // State update
 | 
			
		||||
    currentState = nextState;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,23 +8,52 @@ import { Result, Ok, Err, Option, Some, None } from "./thirdparty/ts-result-es";
 | 
			
		||||
// 13, 14, 15, 16
 | 
			
		||||
 | 
			
		||||
const TURTLE_SIZE = 16;
 | 
			
		||||
const CRAFT_OUTPUT_SLOT = 4;
 | 
			
		||||
// const CRAFT_SLOT_CNT = 9;
 | 
			
		||||
const CRAFT_SLOT_TABLE: number[] = [1, 2, 3, 5, 6, 7, 9, 10, 11];
 | 
			
		||||
// const REST_SLOT_CNT = 7;
 | 
			
		||||
// const REST_SLOT_TABLE: number[] = [4, 8, 12, 13, 14, 15, 16];
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents the NBT data of a Create mod package. This data is used for managing crafting and logistics,
 | 
			
		||||
 * especially in the context of multi-step crafting orders.
 | 
			
		||||
 * The structure is inspired by the logic in Create's own packaging and repackaging helpers.
 | 
			
		||||
 * @see https://github.com/Creators-of-Create/Create/blob/mc1.21.1/dev/src/main/java/com/simibubi/create/content/logistics/packager/repackager/PackageRepackageHelper.java
 | 
			
		||||
 */
 | 
			
		||||
interface CreatePackageTag {
 | 
			
		||||
  /**
 | 
			
		||||
   * The items contained within this package.
 | 
			
		||||
   */
 | 
			
		||||
  Items: {
 | 
			
		||||
    /**
 | 
			
		||||
     * A list of the items stored in the package.
 | 
			
		||||
     */
 | 
			
		||||
    Items: {
 | 
			
		||||
      id: string;
 | 
			
		||||
      Count: number;
 | 
			
		||||
      Slot: number;
 | 
			
		||||
    }[];
 | 
			
		||||
    /**
 | 
			
		||||
     * The number of slots in the package's inventory.
 | 
			
		||||
     */
 | 
			
		||||
    Size: number;
 | 
			
		||||
  };
 | 
			
		||||
  /**
 | 
			
		||||
   * Information about this package's role as a fragment of a larger crafting order.
 | 
			
		||||
   * This is used to track progress and manage dependencies in a distributed crafting system.
 | 
			
		||||
   */
 | 
			
		||||
  Fragment: {
 | 
			
		||||
    /**
 | 
			
		||||
     * The index of this fragment within the larger order.
 | 
			
		||||
     */
 | 
			
		||||
    Index: number;
 | 
			
		||||
    /**
 | 
			
		||||
     * The context of the overall order this fragment belongs to.
 | 
			
		||||
     */
 | 
			
		||||
    OrderContext: {
 | 
			
		||||
      /**
 | 
			
		||||
       * A list of crafting recipes required for the order.
 | 
			
		||||
       */
 | 
			
		||||
      OrderedCrafts: {
 | 
			
		||||
        Pattern: {
 | 
			
		||||
          Entries: {
 | 
			
		||||
@@ -38,6 +67,9 @@ interface CreatePackageTag {
 | 
			
		||||
        };
 | 
			
		||||
        Count: number;
 | 
			
		||||
      }[];
 | 
			
		||||
      /**
 | 
			
		||||
       * A list of pre-existing item stacks required for the order.
 | 
			
		||||
       */
 | 
			
		||||
      OrderedStacks: {
 | 
			
		||||
        Entries: {
 | 
			
		||||
          Item: {
 | 
			
		||||
@@ -48,11 +80,26 @@ interface CreatePackageTag {
 | 
			
		||||
        }[];
 | 
			
		||||
      };
 | 
			
		||||
    };
 | 
			
		||||
    /**
 | 
			
		||||
     * Whether this is the final fragment in the sequence for this specific part of the order.
 | 
			
		||||
     */
 | 
			
		||||
    IsFinal: number;
 | 
			
		||||
    /**
 | 
			
		||||
     * The unique identifier for the overall order.
 | 
			
		||||
     */
 | 
			
		||||
    OrderId: number;
 | 
			
		||||
    /**
 | 
			
		||||
     * The index of this package in a linked list of packages for the same order.
 | 
			
		||||
     */
 | 
			
		||||
    LinkIndex: number;
 | 
			
		||||
    /**
 | 
			
		||||
     * Whether this is the last package in the linked list.
 | 
			
		||||
     */
 | 
			
		||||
    IsFinalLink: number;
 | 
			
		||||
  };
 | 
			
		||||
  /**
 | 
			
		||||
   * The destination address for this package.
 | 
			
		||||
   */
 | 
			
		||||
  Address: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -71,17 +118,25 @@ interface CraftRecipe {
 | 
			
		||||
 | 
			
		||||
interface InventorySlotInfo {
 | 
			
		||||
  name: string;
 | 
			
		||||
  count: number;
 | 
			
		||||
  slotCountQueue: Queue<{
 | 
			
		||||
    slotNum: number;
 | 
			
		||||
    count: number;
 | 
			
		||||
  }>;
 | 
			
		||||
  maxCount: number;
 | 
			
		||||
  slotNum: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CraftMode = "keep" | "keepProduct" | "keepIngredient";
 | 
			
		||||
 | 
			
		||||
class CraftManager {
 | 
			
		||||
  private localName: string;
 | 
			
		||||
  private inventory: InventoryPeripheral;
 | 
			
		||||
 | 
			
		||||
  constructor(modem: WiredModemPeripheral | string) {
 | 
			
		||||
  private inventoryItemsMap = new Map<string, InventorySlotInfo>();
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    modem: WiredModemPeripheral | string,
 | 
			
		||||
    srcInventory: InventoryPeripheral,
 | 
			
		||||
  ) {
 | 
			
		||||
    if (turtle == undefined) {
 | 
			
		||||
      throw new Error("Script must be run in a turtle computer");
 | 
			
		||||
    }
 | 
			
		||||
@@ -105,20 +160,9 @@ class CraftManager {
 | 
			
		||||
    }
 | 
			
		||||
    this.localName = name;
 | 
			
		||||
    // log.info(`Get turtle name : ${name}`);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public pushAll(outputInventory: InventoryPeripheral): void {
 | 
			
		||||
    for (let i = 1; i <= TURTLE_SIZE; i++) {
 | 
			
		||||
      outputInventory.pullItems(this.localName, i);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public craft(dstInventory?: InventoryPeripheral, limit?: number): void {
 | 
			
		||||
    turtle.craft(limit);
 | 
			
		||||
 | 
			
		||||
    if (dstInventory != undefined) {
 | 
			
		||||
      dstInventory.pullItems(this.localName, 1, limit);
 | 
			
		||||
    }
 | 
			
		||||
    // Inventory
 | 
			
		||||
    this.inventory = srcInventory;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static getPackageRecipe(
 | 
			
		||||
@@ -142,40 +186,152 @@ class CraftManager {
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public pullItems(
 | 
			
		||||
    recipe: CraftRecipe,
 | 
			
		||||
    srcInventory: InventoryPeripheral,
 | 
			
		||||
    craftCnt: number,
 | 
			
		||||
  ): Result<number> {
 | 
			
		||||
    // Initialize hash map
 | 
			
		||||
    const ingredientList = srcInventory.list();
 | 
			
		||||
    const ingredientMap = new Map<string, Queue<InventorySlotInfo>>();
 | 
			
		||||
  public initItemsMap() {
 | 
			
		||||
    const ingredientList = this.inventory.list();
 | 
			
		||||
    for (const key in ingredientList) {
 | 
			
		||||
      const slotNum = parseInt(key);
 | 
			
		||||
      const item = srcInventory.getItemDetail(slotNum)!;
 | 
			
		||||
      const item = this.inventory.getItemDetail(slotNum)!;
 | 
			
		||||
 | 
			
		||||
      if (ingredientMap.has(item.name)) {
 | 
			
		||||
        ingredientMap.get(item.name)!.enqueue({
 | 
			
		||||
          name: item.name,
 | 
			
		||||
      if (this.inventoryItemsMap.has(item.name)) {
 | 
			
		||||
        this.inventoryItemsMap.get(item.name)!.slotCountQueue.enqueue({
 | 
			
		||||
          slotNum: slotNum,
 | 
			
		||||
          count: item.count,
 | 
			
		||||
          maxCount: item.maxCount,
 | 
			
		||||
        });
 | 
			
		||||
      } else {
 | 
			
		||||
        ingredientMap.set(
 | 
			
		||||
          item.name,
 | 
			
		||||
          new Queue<InventorySlotInfo>([
 | 
			
		||||
            {
 | 
			
		||||
              name: item.name,
 | 
			
		||||
              slotNum: slotNum,
 | 
			
		||||
              count: item.count,
 | 
			
		||||
              maxCount: item.maxCount,
 | 
			
		||||
            },
 | 
			
		||||
        this.inventoryItemsMap.set(item.name, {
 | 
			
		||||
          name: item.name,
 | 
			
		||||
          maxCount: item.maxCount,
 | 
			
		||||
          slotCountQueue: new Queue<{ slotNum: number; count: number }>([
 | 
			
		||||
            { slotNum: slotNum, count: item.count },
 | 
			
		||||
          ]),
 | 
			
		||||
        );
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public pullFromInventory(
 | 
			
		||||
    itemId: string,
 | 
			
		||||
    count?: number,
 | 
			
		||||
    toSlot?: number,
 | 
			
		||||
  ): Result<number> {
 | 
			
		||||
    const item = this.inventoryItemsMap.get(itemId);
 | 
			
		||||
    if (item === undefined || item.slotCountQueue.size() === 0)
 | 
			
		||||
      return new Err(Error(`No item match ${itemId}`));
 | 
			
		||||
 | 
			
		||||
    if (count === undefined) {
 | 
			
		||||
      const itemSlot = item.slotCountQueue.dequeue()!;
 | 
			
		||||
      const pullItemsCnt = this.inventory.pushItems(
 | 
			
		||||
        this.localName,
 | 
			
		||||
        itemSlot.slotNum,
 | 
			
		||||
        itemSlot.count,
 | 
			
		||||
        toSlot,
 | 
			
		||||
      );
 | 
			
		||||
      return new Ok(pullItemsCnt);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let restCount = count;
 | 
			
		||||
    while (restCount > 0 && item.slotCountQueue.size() > 0) {
 | 
			
		||||
      const itemSlot = item.slotCountQueue.dequeue()!;
 | 
			
		||||
      const pullItemsCnt = this.inventory.pushItems(
 | 
			
		||||
        this.localName,
 | 
			
		||||
        itemSlot.slotNum,
 | 
			
		||||
        Math.min(restCount, itemSlot.count),
 | 
			
		||||
        toSlot,
 | 
			
		||||
      );
 | 
			
		||||
      if (pullItemsCnt < itemSlot.count) {
 | 
			
		||||
        item.slotCountQueue.enqueue({
 | 
			
		||||
          slotNum: itemSlot.slotNum,
 | 
			
		||||
          count: itemSlot.count - pullItemsCnt,
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
      restCount -= pullItemsCnt;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return new Ok(count - restCount);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public pushToInventoryEmpty(
 | 
			
		||||
    fromSlot: number,
 | 
			
		||||
    count?: number,
 | 
			
		||||
  ): Result<number> {
 | 
			
		||||
    let emptySlot = 0;
 | 
			
		||||
    for (let i = this.inventory.size(); i > 0; i--) {
 | 
			
		||||
      const isEmpty = this.inventory.getItemDetail(i) === undefined;
 | 
			
		||||
      if (isEmpty) {
 | 
			
		||||
        emptySlot = i;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (emptySlot <= 0) return new Err(Error("No empty slot found"));
 | 
			
		||||
 | 
			
		||||
    return new Ok(
 | 
			
		||||
      this.inventory.pullItems(this.localName, fromSlot, count, emptySlot),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public pushToInventory(fromSlot: number): Result<number> {
 | 
			
		||||
    const itemInfoDetail = turtle.getItemDetail(fromSlot) as
 | 
			
		||||
      | SlotDetail
 | 
			
		||||
      | undefined;
 | 
			
		||||
    if (itemInfoDetail === undefined) return new Ok(0);
 | 
			
		||||
    const inventoryItemInfo = this.inventoryItemsMap.get(itemInfoDetail.name);
 | 
			
		||||
 | 
			
		||||
    if (inventoryItemInfo === undefined) {
 | 
			
		||||
      return this.pushToInventoryEmpty(fromSlot, itemInfoDetail.count);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let restItemsCount = itemInfoDetail.count;
 | 
			
		||||
    for (const slotInfo of inventoryItemInfo.slotCountQueue) {
 | 
			
		||||
      const pullItemsCount = inventoryItemInfo.maxCount - slotInfo.count;
 | 
			
		||||
      if (pullItemsCount > 0) {
 | 
			
		||||
        this.inventory.pullItems(
 | 
			
		||||
          this.localName,
 | 
			
		||||
          fromSlot,
 | 
			
		||||
          pullItemsCount,
 | 
			
		||||
          slotInfo.slotNum,
 | 
			
		||||
        );
 | 
			
		||||
        restItemsCount -= pullItemsCount;
 | 
			
		||||
        if (restItemsCount <= 0) break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (restItemsCount > 0) {
 | 
			
		||||
      const pushRet = this.pushToInventoryEmpty(fromSlot, restItemsCount);
 | 
			
		||||
      if (pushRet.isErr()) return pushRet;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return new Ok(itemInfoDetail.count);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public clearTurtle(slots?: number[]): void {
 | 
			
		||||
    if (slots !== undefined) {
 | 
			
		||||
      for (const slotNum of slots) {
 | 
			
		||||
        this.pushToInventory(slotNum);
 | 
			
		||||
      }
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (let i = 1; i <= TURTLE_SIZE; i++) {
 | 
			
		||||
      this.pushToInventory(i);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public craft(limit?: number, outputSlot = CRAFT_OUTPUT_SLOT): ItemDetail {
 | 
			
		||||
    turtle.select(outputSlot);
 | 
			
		||||
    turtle.craft(limit);
 | 
			
		||||
    const craftItemDetail = turtle.getItemDetail(
 | 
			
		||||
      outputSlot,
 | 
			
		||||
      true,
 | 
			
		||||
    ) as ItemDetail;
 | 
			
		||||
 | 
			
		||||
    return craftItemDetail;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public pullItemsWithRecipe(
 | 
			
		||||
    recipe: CraftRecipe,
 | 
			
		||||
    craftCnt: number,
 | 
			
		||||
  ): Result<number> {
 | 
			
		||||
    let maxCraftCnt = craftCnt;
 | 
			
		||||
    for (const index in recipe.PatternEntries) {
 | 
			
		||||
      const entry = recipe.PatternEntries[index];
 | 
			
		||||
@@ -183,58 +339,24 @@ class CraftManager {
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (!ingredientMap.has(entry.Item.id))
 | 
			
		||||
      const ingredient = this.inventoryItemsMap.get(entry.Item.id);
 | 
			
		||||
      if (ingredient === undefined)
 | 
			
		||||
        return new Err(Error(`No ingredient match ${entry.Item.id}`));
 | 
			
		||||
 | 
			
		||||
      const ingredient = ingredientMap.get(entry.Item.id)!;
 | 
			
		||||
      let restCraftCnt = maxCraftCnt;
 | 
			
		||||
      while (restCraftCnt > 0 && ingredient.size() > 0) {
 | 
			
		||||
        const slotItem = ingredient.dequeue()!;
 | 
			
		||||
 | 
			
		||||
        // Check item max stack count
 | 
			
		||||
        if (slotItem.maxCount < maxCraftCnt) {
 | 
			
		||||
          maxCraftCnt = slotItem.maxCount;
 | 
			
		||||
          restCraftCnt = maxCraftCnt;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (slotItem.count >= restCraftCnt) {
 | 
			
		||||
          const pushItemsCnt = srcInventory.pushItems(
 | 
			
		||||
            this.localName,
 | 
			
		||||
            slotItem.slotNum,
 | 
			
		||||
            restCraftCnt,
 | 
			
		||||
            CRAFT_SLOT_TABLE[index],
 | 
			
		||||
          );
 | 
			
		||||
          if (pushItemsCnt !== restCraftCnt)
 | 
			
		||||
            return new Err(
 | 
			
		||||
              Error(
 | 
			
		||||
                `Try to get items ${restCraftCnt}x "${slotItem.name}" from inventory, but only get ${pushItemsCnt}x`,
 | 
			
		||||
              ),
 | 
			
		||||
            );
 | 
			
		||||
          if (slotItem.count > restCraftCnt) {
 | 
			
		||||
            ingredient.enqueue({
 | 
			
		||||
              ...slotItem,
 | 
			
		||||
              count: slotItem.count - restCraftCnt,
 | 
			
		||||
            });
 | 
			
		||||
          }
 | 
			
		||||
          restCraftCnt = 0;
 | 
			
		||||
        } else {
 | 
			
		||||
          const pushItemsCnt = srcInventory.pushItems(
 | 
			
		||||
            this.localName,
 | 
			
		||||
            slotItem.slotNum,
 | 
			
		||||
            slotItem.count,
 | 
			
		||||
            CRAFT_SLOT_TABLE[index],
 | 
			
		||||
          );
 | 
			
		||||
          if (pushItemsCnt !== slotItem.count)
 | 
			
		||||
            return new Err(
 | 
			
		||||
              Error(
 | 
			
		||||
                `Try to get items ${slotItem.count}x "${slotItem.name}" from inventory, but only get ${pushItemsCnt}x`,
 | 
			
		||||
              ),
 | 
			
		||||
            );
 | 
			
		||||
          restCraftCnt -= slotItem.count;
 | 
			
		||||
        }
 | 
			
		||||
      // Check item max stack count
 | 
			
		||||
      if (ingredient.maxCount < maxCraftCnt) {
 | 
			
		||||
        maxCraftCnt = ingredient.maxCount;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (restCraftCnt > 0)
 | 
			
		||||
      // Pull items
 | 
			
		||||
      const pullItemsCnt = this.pullFromInventory(
 | 
			
		||||
        ingredient.name,
 | 
			
		||||
        maxCraftCnt,
 | 
			
		||||
        CRAFT_SLOT_TABLE[index],
 | 
			
		||||
      );
 | 
			
		||||
      if (pullItemsCnt.isErr()) return pullItemsCnt;
 | 
			
		||||
 | 
			
		||||
      if (pullItemsCnt.value < maxCraftCnt)
 | 
			
		||||
        return new Err(Error("Not enough items in inventory"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,14 @@ export class Queue<T> {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public enqueue(data: T): void {
 | 
			
		||||
  public enqueue(data: T | T[]): void {
 | 
			
		||||
    if (Array.isArray(data)) {
 | 
			
		||||
      for (const val of data) {
 | 
			
		||||
        this.enqueue(val);
 | 
			
		||||
      }
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const node = new Node(data);
 | 
			
		||||
 | 
			
		||||
    if (this._head === undefined) {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								types/advanced-peripherals/shared.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								types/advanced-peripherals/shared.d.ts
									
									
									
									
										vendored
									
									
								
							@@ -6,7 +6,7 @@ declare interface BlockItemDetailData {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
declare interface BlockDetailData {
 | 
			
		||||
  Items: Record<number, BlockItemDetailData>;
 | 
			
		||||
  Items: Record<string, BlockItemDetailData>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user