mirror of
https://github.com/SikongJueluo/cc-utils.git
synced 2025-11-05 03:37: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:
@@ -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"));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user