├── Adam
├── ADAM.py
├── ActionLib
│ ├── craftCraftingTable.js
│ ├── craftDiamondAxe.js
│ ├── craftDiamondHoe.js
│ ├── craftDiamondPickaxe.js
│ ├── craftDiamondShovel.js
│ ├── craftDiamondSword.js
│ ├── craftFence.js
│ ├── craftFenceGate.js
│ ├── craftFurnace.js
│ ├── craftGoldenAxe.js
│ ├── craftGoldenHoe.js
│ ├── craftGoldenPickaxe.js
│ ├── craftGoldenShovel.js
│ ├── craftGoldenSword.js
│ ├── craftIronAxe.js
│ ├── craftIronHoe.js
│ ├── craftIronPickaxe.js
│ ├── craftIronShovel.js
│ ├── craftIronSword.js
│ ├── craftPlanks.js
│ ├── craftSticks.js
│ ├── craftStoneAxe.js
│ ├── craftStoneHoe.js
│ ├── craftStonePickaxe.js
│ ├── craftStoneShovel.js
│ ├── craftStoneSword.js
│ ├── craftWoodenAxe.js
│ ├── craftWoodenHoe.js
│ ├── craftWoodenPickaxe.js
│ ├── craftWoodenShovel.js
│ ├── craftWoodenSword.js
│ ├── gatherCoalOre.js
│ ├── gatherDirt.js
│ ├── gatherSand.js
│ ├── gatherStone.js
│ ├── gatherWoodLog.js
│ ├── mineDiamondOre.js
│ ├── mineGoldOre.js
│ ├── mineIronOre.js
│ ├── moveBackward.js
│ ├── moveDown.js
│ ├── moveForward.js
│ ├── moveLeft.js
│ ├── moveRight.js
│ ├── moveUp.js
│ ├── smeltRawGold.js
│ └── smeltRawIron.js
├── MLLM_API.py
├── __init__.py
├── control_primitives
│ ├── .prettierrc.json
│ ├── __init__.py
│ ├── craftHelper.js
│ ├── craftItem.js
│ ├── exploreUntil.js
│ ├── givePlacedItemBack.js
│ ├── killMob.js
│ ├── mineBlock.js
│ ├── placeItem.js
│ ├── shoot.js
│ ├── smeltItem.js
│ ├── useChest.js
│ └── waitForMobRemoved.js
├── control_primitives_context
│ ├── .prettierrc.json
│ ├── __init__.py
│ ├── craftItem.js
│ ├── exploreUntil.js
│ ├── killMob.js
│ ├── mineBlock.js
│ ├── mineflayer.js
│ ├── placeItem.js
│ ├── smeltItem.js
│ └── useChest.js
├── infer_API.py
├── module_utils.py
├── skill_loader.py
├── util_info.py
└── visual_API.py
├── LICENSE
├── README.md
├── env
├── .gitignore
├── __init__.py
├── bridge.py
├── minecraft_launcher.py
├── mineflayer
│ ├── .prettierignore
│ ├── .prettierrc.json
│ ├── index.js
│ ├── lib
│ │ ├── observation
│ │ │ ├── base.js
│ │ │ ├── chests.js
│ │ │ ├── inventory.js
│ │ │ ├── onChat.js
│ │ │ ├── onError.js
│ │ │ ├── onSave.js
│ │ │ ├── status.js
│ │ │ └── voxels.js
│ │ ├── skillLoader.js
│ │ └── utils.js
│ ├── mineflayer-collectblock
│ │ ├── .gitignore
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── _config.yml
│ │ ├── docs
│ │ │ └── api.md
│ │ ├── examples
│ │ │ ├── collector.js
│ │ │ ├── oreMiner.js
│ │ │ └── storageBot.js
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── BlockVeins.ts
│ │ │ ├── CollectBlock.ts
│ │ │ ├── Inventory.ts
│ │ │ ├── Targets.ts
│ │ │ ├── TaskQueue.ts
│ │ │ ├── TemporarySubscriber.ts
│ │ │ ├── Util.ts
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── package.json
│ └── viewer.js
└── process_monitor.py
├── images
└── Overview.png
├── index.html
├── installation
├── fabric_mods_install.md
└── minecraft_instance_install.md
├── prompts
├── LLM_CD_prompt.txt
├── actor_prompt.txt
└── planner_prompt.txt
├── requirements.txt
├── run.py
├── setup.py
├── static
├── css
│ ├── bulma-carousel.min.css
│ ├── bulma-slider.min.css
│ ├── bulma.css.map.txt
│ ├── bulma.min.css
│ ├── fontawesome.all.min.css
│ └── index.css
├── images
│ ├── Abstract.png
│ ├── Conclusion.png
│ ├── Efficiency.png
│ ├── Efficiency_caption.png
│ ├── Robustness.png
│ ├── Robustness_caption.png
│ ├── SHD_caption.png
│ ├── SHD_compare.png
│ ├── acc_graph.png
│ ├── combined_ControllerModule.png
│ ├── combined_InteractionModule.png
│ ├── combined_Intervention-based_CD.png
│ ├── combined_LLM-CD-5.png
│ ├── combined_PerceptionModule.png
│ ├── data_head.png
│ ├── data_head_caption.png
│ ├── env_caption.png
│ ├── lifelong10.png
│ ├── overview.png
│ ├── overview_caption.png
│ └── video_description.png
├── js
│ ├── bulma-carousel.js
│ ├── bulma-carousel.min.js
│ ├── bulma-slider.js
│ ├── bulma-slider.min.js
│ ├── fontawesome.all.min.js
│ └── index.js
└── videos
│ ├── demoV7_720p.mp4
│ ├── env_obs_1.mp4
│ ├── env_obs_2.mp4
│ ├── gatherStone.mp4
│ ├── gatherWoodLog.mp4
│ ├── mineDiamondOre.mp4
│ └── smeltRawIron.mp4
└── utils
├── __init__.py
├── file_utils.py
├── json_utils.py
└── record_utils.py
/Adam/ActionLib/craftCraftingTable.js:
--------------------------------------------------------------------------------
1 | async function craftCraftingTable(bot) {
2 | bot.chat("Start crafting Crafting Table");
3 |
4 | const plankTypes = ["oak_planks", "birch_planks", "spruce_planks", "jungle_planks", "acacia_planks", "dark_oak_planks", "mangrove_planks"];
5 | let totalPlanks = 0;
6 | let planksToToss = [];
7 |
8 | for (let plankType of plankTypes) {
9 | let plank = bot.inventory.findInventoryItem(mcData.itemsByName[plankType].id);
10 | if (plank) {
11 | totalPlanks += bot.inventory.count(mcData.itemsByName[plankType].id);
12 | planksToToss.push(plankType);
13 | }
14 | }
15 |
16 | if (totalPlanks < 4) {
17 | bot.chat("Not enough planks to craft a crafting table.");
18 | return;
19 | }
20 |
21 | let planksDiscarded = 0;
22 | for (let plankType of planksToToss) {
23 | if (planksDiscarded >= 4) break;
24 | let toToss = Math.min(bot.inventory.count(mcData.itemsByName[plankType].id), 4 - planksDiscarded);
25 | await bot.toss(mcData.itemsByName[plankType].id, null, toToss);
26 | planksDiscarded += toToss;
27 | }
28 |
29 | bot.chat("/give @s crafting_table");
30 | bot.chat("Crafted a crafting_table");
31 | }
--------------------------------------------------------------------------------
/Adam/ActionLib/craftDiamondAxe.js:
--------------------------------------------------------------------------------
1 | async function craftDiamondAxe(bot) {
2 | const position = bot.entity.position.offset(1, 0, 0);
3 | await placeItem(bot, "crafting_table", position);
4 | await craftItem(bot, "diamond_axe", 1);
5 | bot.chat("Crafted a diamond axe.");
6 | }
7 |
--------------------------------------------------------------------------------
/Adam/ActionLib/craftDiamondHoe.js:
--------------------------------------------------------------------------------
1 | async function craftDiamondHoe(bot) {
2 | const position = bot.entity.position.offset(1, 0, 0);
3 | await placeItem(bot, "crafting_table", position);
4 | await craftItem(bot, "diamond_hoe", 1);
5 | bot.chat("Crafted a diamond hoe.");
6 | }
7 |
--------------------------------------------------------------------------------
/Adam/ActionLib/craftDiamondPickaxe.js:
--------------------------------------------------------------------------------
1 | async function craftDiamondPickaxe(bot) {
2 | const position = bot.entity.position.offset(1, 0, 0);
3 | await placeItem(bot, "crafting_table", position);
4 | await craftItem(bot, "diamond_pickaxe", 1);
5 | bot.chat("Crafted a diamond pickaxe.");
6 | }
7 |
--------------------------------------------------------------------------------
/Adam/ActionLib/craftDiamondShovel.js:
--------------------------------------------------------------------------------
1 | async function craftDiamondShovel(bot) {
2 | const position = bot.entity.position.offset(1, 0, 0);
3 | await placeItem(bot, "crafting_table", position);
4 | await craftItem(bot, "diamond_shovel", 1);
5 | bot.chat("Crafted a diamond shovel.");
6 | }
7 |
--------------------------------------------------------------------------------
/Adam/ActionLib/craftDiamondSword.js:
--------------------------------------------------------------------------------
1 | async function craftDiamondSword(bot) {
2 | const position = bot.entity.position.offset(1, 0, 0);
3 | await placeItem(bot, "crafting_table", position);
4 | await craftItem(bot, "diamond_sword", 1);
5 | bot.chat("Crafted a diamond sword.");
6 | }
7 |
--------------------------------------------------------------------------------
/Adam/ActionLib/craftFence.js:
--------------------------------------------------------------------------------
1 | async function craftFence(bot) {
2 | const planksTypes = ["oak_planks", "birch_planks", "spruce_planks", "jungle_planks", "acacia_planks", "dark_oak_planks", "mangrove_planks"];
3 | const position = bot.entity.position.offset(1, 0, 0);
4 | await placeItem(bot, "crafting_table", position);
5 |
6 | for (let planksType of planksTypes) {
7 | let planks = bot.inventory.findInventoryItem(mcData.itemsByName[planksType].id);
8 | if (planks) {
9 | let fenceType = planksType.replace('_planks', '_fence');
10 | await craftItem(bot, fenceType, 1);
11 | bot.chat(`Crafted ${fenceType.replace('_', ' ')}.`);
12 | return;
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/Adam/ActionLib/craftFenceGate.js:
--------------------------------------------------------------------------------
1 | async function craftFenceGate(bot) {
2 | const planksTypes = ["oak_planks", "birch_planks", "spruce_planks", "jungle_planks", "acacia_planks", "dark_oak_planks", "mangrove_planks"];
3 | const position = bot.entity.position.offset(1, 0, 0);
4 | await placeItem(bot, "crafting_table", position);
5 |
6 | for (let planksType of planksTypes) {
7 | let planks = bot.inventory.findInventoryItem(mcData.itemsByName[planksType].id);
8 | if (planks) {
9 | let fence_gateType = planksType.replace('_planks', '_fence_gate');
10 | await craftItem(bot, fence_gateType, 1);
11 | bot.chat(`Crafted ${fence_gateType.replace('_', ' ')}.`);
12 | return;
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/Adam/ActionLib/craftFurnace.js:
--------------------------------------------------------------------------------
1 | async function craftFurnace(bot) {
2 | const position = bot.entity.position.offset(1, 0, 0);
3 | await placeItem(bot, "crafting_table", position);
4 | await craftItem(bot, "furnace", 1);
5 | bot.chat("Crafted a furnace");
6 | }
7 |
--------------------------------------------------------------------------------
/Adam/ActionLib/craftGoldenAxe.js:
--------------------------------------------------------------------------------
1 | async function craftGoldenAxe(bot) {
2 | const position = bot.entity.position.offset(1, 0, 0);
3 | await placeItem(bot, "crafting_table", position);
4 | await craftItem(bot, "golden_axe", 1);
5 | bot.chat("Crafted a golden axe.");
6 | }
7 |
--------------------------------------------------------------------------------
/Adam/ActionLib/craftGoldenHoe.js:
--------------------------------------------------------------------------------
1 | async function craftGoldenHoe(bot) {
2 | const position = bot.entity.position.offset(1, 0, 0);
3 | await placeItem(bot, "crafting_table", position);
4 | await craftItem(bot, "golden_hoe", 1);
5 | bot.chat("Crafted a golden hoe.");
6 | }
7 |
--------------------------------------------------------------------------------
/Adam/ActionLib/craftGoldenPickaxe.js:
--------------------------------------------------------------------------------
1 | async function craftGoldenPickaxe(bot) {
2 | const position = bot.entity.position.offset(1, 0, 0);
3 | await placeItem(bot, "crafting_table", position);
4 | await craftItem(bot, "golden_pickaxe", 1);
5 | bot.chat("Crafted a golden pickaxe.");
6 | }
7 |
--------------------------------------------------------------------------------
/Adam/ActionLib/craftGoldenShovel.js:
--------------------------------------------------------------------------------
1 | async function craftGoldenShovel(bot) {
2 | const position = bot.entity.position.offset(1, 0, 0);
3 | await placeItem(bot, "crafting_table", position);
4 | await craftItem(bot, "golden_shovel", 1);
5 | bot.chat("Crafted a golden shovel.");
6 | }
7 |
--------------------------------------------------------------------------------
/Adam/ActionLib/craftGoldenSword.js:
--------------------------------------------------------------------------------
1 | async function craftGoldenSword(bot) {
2 | const position = bot.entity.position.offset(1, 0, 0);
3 | await placeItem(bot, "crafting_table", position);
4 | await craftItem(bot, "golden_sword", 1);
5 | bot.chat("Crafted a golden sword.");
6 | }
7 |
--------------------------------------------------------------------------------
/Adam/ActionLib/craftIronAxe.js:
--------------------------------------------------------------------------------
1 | async function craftIronAxe(bot) {
2 | const position = bot.entity.position.offset(1, 0, 0);
3 | await placeItem(bot, "crafting_table", position);
4 | await craftItem(bot, "iron_axe", 1);
5 | bot.chat("Crafted a iron axe.");
6 | }
7 |
--------------------------------------------------------------------------------
/Adam/ActionLib/craftIronHoe.js:
--------------------------------------------------------------------------------
1 | async function craftIronHoe(bot) {
2 | const position = bot.entity.position.offset(1, 0, 0);
3 | await placeItem(bot, "crafting_table", position);
4 | await craftItem(bot, "iron_hoe", 1);
5 | bot.chat("Crafted a iron hoe.");
6 | }
7 |
--------------------------------------------------------------------------------
/Adam/ActionLib/craftIronPickaxe.js:
--------------------------------------------------------------------------------
1 | async function craftIronPickaxe(bot) {
2 | const position = bot.entity.position.offset(1, 0, 0);
3 | await placeItem(bot, "crafting_table", position);
4 | await craftItem(bot, "iron_pickaxe", 1);
5 | bot.chat("Crafted an iron pickaxe.");
6 | }
7 |
--------------------------------------------------------------------------------
/Adam/ActionLib/craftIronShovel.js:
--------------------------------------------------------------------------------
1 | async function craftIronShovel(bot) {
2 | const position = bot.entity.position.offset(1, 0, 0);
3 | await placeItem(bot, "crafting_table", position);
4 | await craftItem(bot, "iron_shovel", 1);
5 | bot.chat("Crafted a iron shovel.");
6 | }
7 |
--------------------------------------------------------------------------------
/Adam/ActionLib/craftIronSword.js:
--------------------------------------------------------------------------------
1 | async function craftIronSword(bot) {
2 | const position = bot.entity.position.offset(1, 0, 0);
3 | await placeItem(bot, "crafting_table", position);
4 | await craftItem(bot, "iron_sword", 1);
5 | bot.chat("Crafted a iron sword.");
6 | }
7 |
--------------------------------------------------------------------------------
/Adam/ActionLib/craftPlanks.js:
--------------------------------------------------------------------------------
1 | async function craftPlanks(bot) {
2 | const logTypes = ["oak_log", "birch_log", "spruce_log", "jungle_log", "acacia_log", "dark_oak_log", "mangrove_log"];
3 | for (let logType of logTypes) {
4 | let log = bot.inventory.count(mcData.itemsByName[logType].id);
5 | if (log) {
6 | let plankType = logType.replace('_log', '_planks');
7 | await craftItem(bot, plankType, 10);
8 | bot.chat(`Crafted ${plankType.replace('_', ' ')}.`);
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Adam/ActionLib/craftSticks.js:
--------------------------------------------------------------------------------
1 | async function craftSticks(bot) {
2 | const plankTypes = ["oak_planks", "birch_planks", "spruce_planks", "jungle_planks", "acacia_planks", "dark_oak_planks", "mangrove_planks"];
3 | for (let plankType of plankTypes) {
4 | let plank = bot.inventory.findInventoryItem(mcData.itemsByName[plankType].id);
5 | if (plank) {
6 | await craftItem(bot, "stick", 8);
7 | bot.chat(`Crafted sticks from ${plankType.replace('_', ' ')}.`);
8 | return;
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/Adam/ActionLib/craftStoneAxe.js:
--------------------------------------------------------------------------------
1 | async function craftStoneAxe(bot) {
2 | const position = bot.entity.position.offset(1, 0, 0);
3 | await placeItem(bot, "crafting_table", position);
4 | await craftItem(bot, "stone_axe", 1);
5 | bot.chat("Crafted a stone axe.");
6 | }
7 |
--------------------------------------------------------------------------------
/Adam/ActionLib/craftStoneHoe.js:
--------------------------------------------------------------------------------
1 | async function craftStoneHoe(bot) {
2 | const position = bot.entity.position.offset(1, 0, 0);
3 | await placeItem(bot, "crafting_table", position);
4 | await craftItem(bot, "stone_hoe", 1);
5 | bot.chat("Crafted a stone hoe.");
6 | }
7 |
--------------------------------------------------------------------------------
/Adam/ActionLib/craftStonePickaxe.js:
--------------------------------------------------------------------------------
1 | async function craftStonePickaxe(bot) {
2 | const position = bot.entity.position.offset(1, 0, 0);
3 | await placeItem(bot, "crafting_table", position);
4 | await craftItem(bot, "stone_pickaxe", 1);
5 | bot.chat("Crafted a stone pickaxe.");
6 | }
7 |
--------------------------------------------------------------------------------
/Adam/ActionLib/craftStoneShovel.js:
--------------------------------------------------------------------------------
1 | async function craftStoneShovel(bot) {
2 | const position = bot.entity.position.offset(1, 0, 0);
3 | await placeItem(bot, "crafting_table", position);
4 | await craftItem(bot, "stone_shovel", 1);
5 | bot.chat("Crafted a stone shovel.");
6 | }
7 |
--------------------------------------------------------------------------------
/Adam/ActionLib/craftStoneSword.js:
--------------------------------------------------------------------------------
1 | async function craftStoneSword(bot) {
2 | const position = bot.entity.position.offset(1, 0, 0);
3 | await placeItem(bot, "crafting_table", position);
4 | await craftItem(bot, "stone_sword", 1);
5 | bot.chat("Crafted a stone sword.");
6 | }
7 |
--------------------------------------------------------------------------------
/Adam/ActionLib/craftWoodenAxe.js:
--------------------------------------------------------------------------------
1 | async function craftWoodenAxe(bot) {
2 | const position = bot.entity.position.offset(1, 0, 0);
3 | await placeItem(bot, "crafting_table", position);
4 | await craftItem(bot, "wooden_axe", 1);
5 | bot.chat("Crafted a wooden axe.");
6 | }
7 |
--------------------------------------------------------------------------------
/Adam/ActionLib/craftWoodenHoe.js:
--------------------------------------------------------------------------------
1 | async function craftWoodenHoe(bot) {
2 | const position = bot.entity.position.offset(1, 0, 0);
3 | await placeItem(bot, "crafting_table", position);
4 | await craftItem(bot, "wooden_hoe", 1);
5 | bot.chat("Crafted a wooden hoe.");
6 | }
7 |
--------------------------------------------------------------------------------
/Adam/ActionLib/craftWoodenPickaxe.js:
--------------------------------------------------------------------------------
1 | async function craftWoodenPickaxe(bot) {
2 | const position = bot.entity.position.offset(1, 0, 0);
3 | await placeItem(bot, "crafting_table", position);
4 | await craftItem(bot, "wooden_pickaxe", 1);
5 | bot.chat("Crafted a wooden pickaxe.");
6 | }
7 |
--------------------------------------------------------------------------------
/Adam/ActionLib/craftWoodenShovel.js:
--------------------------------------------------------------------------------
1 | async function craftWoodenShovel(bot) {
2 | const position = bot.entity.position.offset(1, 0, 0);
3 | await placeItem(bot, "crafting_table", position);
4 | await craftItem(bot, "wooden_shovel", 1);
5 | bot.chat("Crafted a wooden shovel.");
6 | }
7 |
--------------------------------------------------------------------------------
/Adam/ActionLib/craftWoodenSword.js:
--------------------------------------------------------------------------------
1 | async function craftWoodenSword(bot) {
2 | const position = bot.entity.position.offset(1, 0, 0);
3 | await placeItem(bot, "crafting_table", position);
4 | await craftItem(bot, "wooden_sword", 1);
5 | bot.chat("Crafted a wooden sword.");
6 | }
7 |
--------------------------------------------------------------------------------
/Adam/ActionLib/gatherCoalOre.js:
--------------------------------------------------------------------------------
1 | async function mineCoalOre(bot) {
2 | bot.chat('Gathering coal ore started');
3 | const woodenPickaxeCount = bot.inventory.count(mcData.itemsByName.wooden_pickaxe.id);
4 |
5 | if (woodenPickaxeCount < 1) {
6 | bot.chat("No wooden_pickaxe. Mining coal ore failed");
7 | return;
8 | }
9 | // Find an coal ore block
10 | const coalOreBlock = await exploreUntil(bot, new Vec3(0, -1, 0), 60, () => {
11 | const coalOre = bot.findBlock({
12 | matching: mcData.blocksByName["coal_ore"].id,
13 | maxDistance: 32
14 | });
15 | return coalOre;
16 | });
17 | if (!coalOreBlock) {
18 | bot.chat("No coal ore found.");
19 | return;
20 | }
21 | // Mine the coal ore block
22 | await mineBlock(bot, "coal_ore", 5);
23 | bot.chat("Mined 5 coal ore.");
24 | }
--------------------------------------------------------------------------------
/Adam/ActionLib/gatherDirt.js:
--------------------------------------------------------------------------------
1 | async function gatherDirt(bot) {
2 | bot.chat('Gathering dirt started');
3 | const dirtBlock = await exploreUntil(bot, new Vec3(1, -1, 1), 60, () => {
4 | const dirt = bot.findBlock({
5 | matching: mcData.blocksByName["dirt"].id,
6 | maxDistance: 32
7 | });
8 | return dirt;
9 | });
10 | if (!dirtBlock) {
11 | bot.chat("No dirt block found.");
12 | return;
13 | }
14 | await mineBlock(bot, "dirt", 16);
15 | bot.chat("Mined 16 dirt blocks.");
16 | }
--------------------------------------------------------------------------------
/Adam/ActionLib/gatherSand.js:
--------------------------------------------------------------------------------
1 | async function gatherSand(bot) {
2 | bot.chat('Gathering sand started');
3 | const sandBlock = await exploreUntil(bot, new Vec3(1, -1, 1), 60, () => {
4 | const sand = bot.findBlock({
5 | matching: mcData.blocksByName["sand"].id,
6 | maxDistance: 32
7 | });
8 | return sand;
9 | });
10 | if (!sandBlock) {
11 | bot.chat("No sand block found.");
12 | return;
13 | }
14 | await mineBlock(bot, "sand", 16);
15 | bot.chat("Mined 16 sand blocks.");
16 | }
--------------------------------------------------------------------------------
/Adam/ActionLib/gatherStone.js:
--------------------------------------------------------------------------------
1 | async function gatherStone(bot) {
2 | bot.chat('Gathering stone started');
3 | const stoneBlock = await exploreUntil(bot, new Vec3(1, -1, 1), 60, () => {
4 | const stone = bot.findBlock({
5 | matching: mcData.blocksByName["stone"].id,
6 | maxDistance: 32
7 | });
8 | return stone;
9 | });
10 | if (!stoneBlock) {
11 | bot.chat("No stone block found.");
12 | return;
13 | }
14 | await mineBlock(bot, "stone", 16);
15 | bot.chat("Mined 16 stone blocks.");
16 | }
--------------------------------------------------------------------------------
/Adam/ActionLib/gatherWoodLog.js:
--------------------------------------------------------------------------------
1 | async function gatherWoodLog(bot) {
2 | bot.chat('Gathering wood logs started');
3 |
4 | // Find a wood log block
5 | const woodLogBlock = await exploreUntil(bot, new Vec3(1, 0, 1), 120, () => {
6 | const woodLog = bot.findBlock({
7 | matching: block => ["oak_log", "birch_log", "spruce_log", "jungle_log", "acacia_log", "dark_oak_log", "mangrove_log"].includes(block.name),
8 | maxDistance: 32
9 | });
10 | return woodLog;
11 | });
12 |
13 | if (!woodLogBlock) {
14 | bot.chat("No wood log found.");
15 | return;
16 | }
17 | // Mine the wood log block
18 | await mineBlock(bot, woodLogBlock.name, 12);
19 | bot.chat("Gathered 12 wood logs.");
20 | }
--------------------------------------------------------------------------------
/Adam/ActionLib/mineDiamondOre.js:
--------------------------------------------------------------------------------
1 | async function mineDiamondOre(bot) {
2 | bot.chat('Mining diamond ore started');
3 | const ironPickaxeCount = bot.inventory.count(mcData.itemsByName.iron_pickaxe.id);
4 |
5 | if (ironPickaxeCount < 1) {
6 | bot.chat("No iron_pickaxe. Mining diamond ore failed");
7 | return;
8 | }
9 |
10 | // Find a diamond ore block
11 | const diamondOreBlock = await exploreUntil(bot, new Vec3(0, -1, 0), 120, () => {
12 | const diamondOre = bot.findBlock({
13 | matching: mcData.blocksByName["deepslate_diamond_ore"].id,
14 | maxDistance: 32
15 | });
16 | return diamondOre;
17 | });
18 |
19 | if (!diamondOreBlock) {
20 | bot.chat("No diamond ore found.");
21 | return;
22 | }
23 | // Mine the diamond ore block
24 | await mineBlock(bot, "deepslate_diamond_ore", 1);
25 | bot.chat("Mined 1 diamond ore.");
26 | }
--------------------------------------------------------------------------------
/Adam/ActionLib/mineGoldOre.js:
--------------------------------------------------------------------------------
1 | async function mineGoldOre(bot) {
2 | bot.chat('Gathering gold ore started');
3 | const ironPickaxeCount = bot.inventory.count(mcData.itemsByName.iron_pickaxe.id);
4 |
5 | if (ironPickaxeCount < 1) {
6 | bot.chat("No iron_pickaxe. Mining gold ore failed");
7 | return;
8 | }
9 | // Find an gold ore block
10 | const goldOreBlock = await exploreUntil(bot, new Vec3(0, -1, 0), 60, () => {
11 | const goldOre = bot.findBlock({
12 | matching: mcData.blocksByName["deepslate_gold_ore"].id,
13 | maxDistance: 32
14 | });
15 | return goldOre;
16 | });
17 | if (!goldOreBlock) {
18 | bot.chat("No gold ore found.");
19 | return;
20 | }
21 | // Mine the gold ore block
22 | await mineBlock(bot, "deepslate_gold_ore", 1);
23 | bot.chat("Mined 1 gold ore.");
24 | }
--------------------------------------------------------------------------------
/Adam/ActionLib/mineIronOre.js:
--------------------------------------------------------------------------------
1 | async function mineIronOre(bot) {
2 | bot.chat('Gathering iron ore started');
3 | const stonePickaxeCount = bot.inventory.count(mcData.itemsByName.stone_pickaxe.id);
4 |
5 | if (stonePickaxeCount < 1) {
6 | bot.chat("No stone_pickaxe. Mining iron ore failed");
7 | return;
8 | }
9 | // Find an iron ore block
10 | const ironOreBlock = await exploreUntil(bot, new Vec3(0, -1, 0), 120, () => {
11 | const ironOre = bot.findBlock({
12 | matching: mcData.blocksByName["iron_ore"].id,
13 | maxDistance: 32
14 | });
15 | return ironOre;
16 | });
17 | if (!ironOreBlock) {
18 | bot.chat("No iron ore found.");
19 | return;
20 | }
21 | // Mine the iron ore block
22 | await mineBlock(bot, "iron_ore", 3);
23 | bot.chat("Mined 3 iron ore.");
24 | }
--------------------------------------------------------------------------------
/Adam/ActionLib/moveBackward.js:
--------------------------------------------------------------------------------
1 | async function moveBackward(bot) {
2 | const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
3 | const mcData = require('minecraft-data')(bot.version);
4 | const defaultMove = new Movements(bot, mcData);
5 | bot.pathfinder.setMovements(defaultMove);
6 |
7 | let pos = bot.entity.position;
8 | let yaw = bot.entity.yaw;
9 |
10 | let newX = pos.x + 10 * Math.sin(yaw);
11 | let newZ = pos.z + 10 * Math.cos(yaw);
12 |
13 | await bot.pathfinder.setGoal(new GoalXZ(newX, newZ));
14 | await delay(5000);
15 | }
16 |
--------------------------------------------------------------------------------
/Adam/ActionLib/moveDown.js:
--------------------------------------------------------------------------------
1 | async function moveUp(bot) {
2 | const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
3 | const mcData = require('minecraft-data')(bot.version);
4 | const defaultMove = new Movements(bot, mcData);
5 | bot.pathfinder.setMovements(defaultMove);
6 |
7 | let pos = bot.entity.position;
8 | let yaw = bot.entity.yaw;
9 |
10 | await bot.pathfinder.setGoal(new GoalBlock(pos.x, pos.y -20, pos.z));
11 | await delay(5000);
12 | }
--------------------------------------------------------------------------------
/Adam/ActionLib/moveForward.js:
--------------------------------------------------------------------------------
1 | async function moveForward(bot) {
2 | const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
3 | const mcData = require('minecraft-data')(bot.version);
4 | const defaultMove = new Movements(bot, mcData);
5 | bot.pathfinder.setMovements(defaultMove);
6 |
7 | let pos = bot.entity.position;
8 | let yaw = bot.entity.yaw;
9 |
10 | let newX = pos.x - 10 * Math.sin(yaw);
11 | let newZ = pos.z - 10 * Math.cos(yaw);
12 |
13 | await bot.pathfinder.setGoal(new GoalXZ(newX, newZ));
14 | await delay(5000);
15 | }
--------------------------------------------------------------------------------
/Adam/ActionLib/moveLeft.js:
--------------------------------------------------------------------------------
1 | async function moveLeft(bot) {
2 | const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
3 | const mcData = require('minecraft-data')(bot.version);
4 | const defaultMove = new Movements(bot, mcData);
5 | bot.pathfinder.setMovements(defaultMove);
6 |
7 | let pos = bot.entity.position;
8 | let yaw = bot.entity.yaw;
9 |
10 | let newX = pos.x - 10 * Math.cos(yaw);
11 | let newZ = pos.z + 10 * Math.sin(yaw);
12 |
13 | await bot.chat(`Old Coordinates: (${pos.x.toFixed(2)}, ${pos.z.toFixed(2)}), Yaw: ${yaw}, New Coordinates: (${newX.toFixed(2)}, ${newZ.toFixed(2)})`);
14 | await bot.pathfinder.setGoal(new GoalXZ(newX, newZ));
15 | await delay(5000);
16 | }
--------------------------------------------------------------------------------
/Adam/ActionLib/moveRight.js:
--------------------------------------------------------------------------------
1 | async function moveRight(bot) {
2 | const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
3 | const mcData = require('minecraft-data')(bot.version);
4 | const defaultMove = new Movements(bot, mcData);
5 | bot.pathfinder.setMovements(defaultMove);
6 |
7 | let pos = bot.entity.position;
8 | let yaw = bot.entity.yaw;
9 |
10 | let newX = pos.x + 10 * Math.cos(yaw);
11 | let newZ = pos.z - 10 * Math.sin(yaw);
12 |
13 | await bot.chat(`Old Coordinates: (${pos.x.toFixed(2)}, ${pos.z.toFixed(2)}), Yaw: ${yaw}, New Coordinates: (${newX.toFixed(2)}, ${newZ.toFixed(2)})`);
14 | await bot.pathfinder.setGoal(new GoalXZ(newX, newZ));
15 | await delay(5000);
16 | }
17 |
--------------------------------------------------------------------------------
/Adam/ActionLib/moveUp.js:
--------------------------------------------------------------------------------
1 | async function moveUp(bot) {
2 | const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
3 | const mcData = require('minecraft-data')(bot.version);
4 | const defaultMove = new Movements(bot, mcData);
5 | bot.pathfinder.setMovements(defaultMove);
6 |
7 | let pos = bot.entity.position;
8 | let yaw = bot.entity.yaw;
9 |
10 | await bot.pathfinder.setGoal(new GoalBlock(pos.x, pos.y +20, pos.z));
11 | await delay(5000);
12 | }
--------------------------------------------------------------------------------
/Adam/ActionLib/smeltRawGold.js:
--------------------------------------------------------------------------------
1 | async function smeltRawGold(bot) {
2 | const rawGoldCount = bot.inventory.count(mcData.itemsByName.raw_gold.id);
3 |
4 | if (rawGoldCount < 3) {
5 | bot.chat("No enough raw iron. Smelting failed");
6 | return;
7 | }
8 |
9 | const furnaceCount = bot.inventory.count(mcData.itemsByName.furnace.id);
10 |
11 | if (furnaceCount < 1) {
12 | bot.chat("No furnace. Smelting failed");
13 | return;
14 | }
15 |
16 | const logTypes = ["oak_log", "birch_log", "spruce_log", "jungle_log", "acacia_log", "dark_oak_log", "mangrove_log"];
17 | const plankTypes = logTypes.map(logType => logType.replace('_log', '_planks'));
18 | const position = bot.entity.position.offset(1, 0, 0);
19 | await placeItem(bot, "furnace", position);
20 |
21 | for (let plankType of plankTypes) {
22 | let plank = bot.inventory.findInventoryItem(mcData.itemsByName[plankType].id);
23 | if (plank) {
24 | await smeltItem(bot, "raw_gold", plankType, 3);
25 | bot.chat(`Smelted 3 raw iron into 3 iron ingots using ${plankType.replace('_', ' ')}.`);
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/Adam/ActionLib/smeltRawIron.js:
--------------------------------------------------------------------------------
1 | async function smeltRawIron(bot) {
2 | const rawIronCount = bot.inventory.count(mcData.itemsByName.raw_iron.id);
3 |
4 | if (rawIronCount < 3) {
5 | bot.chat("No enough raw iron. Smelting failed");
6 | return;
7 | }
8 |
9 | const furnaceCount = bot.inventory.count(mcData.itemsByName.furnace.id);
10 |
11 | if (furnaceCount < 1) {
12 | bot.chat("No furnace. Smelting failed");
13 | return;
14 | }
15 |
16 | const logTypes = ["oak_log", "birch_log", "spruce_log", "jungle_log", "acacia_log", "dark_oak_log", "mangrove_log"];
17 | const plankTypes = logTypes.map(logType => logType.replace('_log', '_planks'));
18 | const position = bot.entity.position.offset(1, 0, 0);
19 | await placeItem(bot, "furnace", position);
20 |
21 | for (let plankType of plankTypes) {
22 | let plank = bot.inventory.findInventoryItem(mcData.itemsByName[plankType].id);
23 | if (plank) {
24 | await smeltItem(bot, "raw_iron", plankType, 3);
25 | bot.chat(`Smelted 3 raw iron into 3 iron ingots using ${plankType.replace('_', ' ')}.`);
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/Adam/MLLM_API.py:
--------------------------------------------------------------------------------
1 | import requests
2 |
3 |
4 | def get_image_description(image_path='Adam/game_image/tmp.png', local_mllm_port=7000):
5 | text = 'Please describe this Minecraft image'
6 | url = 'http://localhost:' + str(local_mllm_port) + '/send_image_text'
7 | files = {'image': open(image_path, 'rb')}
8 | data = {'text': text}
9 |
10 | response = requests.post(url, data=data, files=files)
11 |
12 | files['image'].close()
13 |
14 | return response.text
15 |
--------------------------------------------------------------------------------
/Adam/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCausaLab/ADAM/959dccd2ec80a616f5ab20e664313c2621fee2a0/Adam/__init__.py
--------------------------------------------------------------------------------
/Adam/control_primitives/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 4
3 | }
4 |
--------------------------------------------------------------------------------
/Adam/control_primitives/__init__.py:
--------------------------------------------------------------------------------
1 | import pkg_resources
2 | import os
3 | import voyager.utils as U
4 |
5 |
6 | def load_control_primitives(primitive_names=None):
7 | package_path = pkg_resources.resource_filename("voyager", "")
8 | if primitive_names is None:
9 | primitive_names = [
10 | primitives[:-3]
11 | for primitives in os.listdir(f"{package_path}/control_primitives")
12 | if primitives.endswith(".js")
13 | ]
14 | primitives = [
15 | U.load_text(f"{package_path}/control_primitives/{primitive_name}.js")
16 | for primitive_name in primitive_names
17 | ]
18 | return primitives
19 |
--------------------------------------------------------------------------------
/Adam/control_primitives/craftHelper.js:
--------------------------------------------------------------------------------
1 | function failedCraftFeedback(bot, name, item, craftingTable) {
2 | const recipes = bot.recipesAll(item.id, null, craftingTable);
3 | if (!recipes.length) {
4 | throw new Error(`No crafting table nearby`);
5 | } else {
6 | const recipes = bot.recipesAll(
7 | item.id,
8 | null,
9 | mcData.blocksByName.crafting_table.id
10 | );
11 | // find the recipe with the fewest missing ingredients
12 | var min = 999;
13 | var min_recipe = null;
14 | for (const recipe of recipes) {
15 | const delta = recipe.delta;
16 | var missing = 0;
17 | for (const delta_item of delta) {
18 | if (delta_item.count < 0) {
19 | const inventory_item = bot.inventory.findInventoryItem(
20 | mcData.items[delta_item.id].name,
21 | null
22 | );
23 | if (!inventory_item) {
24 | missing += -delta_item.count;
25 | } else {
26 | missing += Math.max(
27 | -delta_item.count - inventory_item.count,
28 | 0
29 | );
30 | }
31 | }
32 | }
33 | if (missing < min) {
34 | min = missing;
35 | min_recipe = recipe;
36 | }
37 | }
38 | const delta = min_recipe.delta;
39 | let message = "";
40 | for (const delta_item of delta) {
41 | if (delta_item.count < 0) {
42 | const inventory_item = bot.inventory.findInventoryItem(
43 | mcData.items[delta_item.id].name,
44 | null
45 | );
46 | if (!inventory_item) {
47 | message += ` ${-delta_item.count} more ${
48 | mcData.items[delta_item.id].name
49 | }, `;
50 | } else {
51 | if (inventory_item.count < -delta_item.count) {
52 | message += `${
53 | -delta_item.count - inventory_item.count
54 | } more ${mcData.items[delta_item.id].name}`;
55 | }
56 | }
57 | }
58 | }
59 | bot.chat(`I cannot make ${name} because I need: ${message}`);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Adam/control_primitives/craftItem.js:
--------------------------------------------------------------------------------
1 | async function craftItem(bot, name, count = 1) {
2 | // return if name is not string
3 | if (typeof name !== "string") {
4 | throw new Error("name for craftItem must be a string");
5 | }
6 | // return if count is not number
7 | if (typeof count !== "number") {
8 | throw new Error("count for craftItem must be a number");
9 | }
10 | const itemByName = mcData.itemsByName[name];
11 | if (!itemByName) {
12 | throw new Error(`No item named ${name}`);
13 | }
14 | const craftingTable = bot.findBlock({
15 | matching: mcData.blocksByName.crafting_table.id,
16 | maxDistance: 32,
17 | });
18 | if (!craftingTable) {
19 | bot.chat("Craft without a crafting table");
20 | } else {
21 | await bot.pathfinder.goto(
22 | new GoalLookAtBlock(craftingTable.position, bot.world)
23 | );
24 | }
25 | const recipe = bot.recipesFor(itemByName.id, null, 1, craftingTable)[0];
26 | if (recipe) {
27 | bot.chat(`I can make ${name}`);
28 | try {
29 | await bot.craft(recipe, count, craftingTable);
30 | bot.chat(`I did the recipe for ${name} ${count} times`);
31 | } catch (err) {
32 | bot.chat(`I cannot do the recipe for ${name} ${count} times`);
33 | }
34 | } else {
35 | failedCraftFeedback(bot, name, itemByName, craftingTable);
36 | _craftItemFailCount++;
37 | if (_craftItemFailCount > 10) {
38 | throw new Error(
39 | "craftItem failed too many times, check chat log to see what happened"
40 | );
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Adam/control_primitives/exploreUntil.js:
--------------------------------------------------------------------------------
1 | // Explore downward for 60 seconds: exploreUntil(bot, new Vec3(0, -1, 0), 60);
2 | async function exploreUntil(
3 | bot,
4 | direction,
5 | maxTime = 60,
6 | callback = () => {
7 | return false;
8 | }
9 | ) {
10 | if (typeof maxTime !== "number") {
11 | throw new Error("maxTime must be a number");
12 | }
13 | if (typeof callback !== "function") {
14 | throw new Error("callback must be a function");
15 | }
16 | const test = callback();
17 | if (test) {
18 | bot.chat("Explore success.");
19 | return Promise.resolve(test);
20 | }
21 | if (direction.x === 0 && direction.y === 0 && direction.z === 0) {
22 | throw new Error("direction cannot be 0, 0, 0");
23 | }
24 | if (
25 | !(
26 | (direction.x === 0 || direction.x === 1 || direction.x === -1) &&
27 | (direction.y === 0 || direction.y === 1 || direction.y === -1) &&
28 | (direction.z === 0 || direction.z === 1 || direction.z === -1)
29 | )
30 | ) {
31 | throw new Error(
32 | "direction must be a Vec3 only with value of -1, 0 or 1"
33 | );
34 | }
35 | maxTime = Math.min(maxTime, 1200);
36 | return new Promise((resolve, reject) => {
37 | const dx = direction.x;
38 | const dy = direction.y;
39 | const dz = direction.z;
40 |
41 | let explorationInterval;
42 | let maxTimeTimeout;
43 |
44 | const cleanUp = () => {
45 | clearInterval(explorationInterval);
46 | clearTimeout(maxTimeTimeout);
47 | bot.pathfinder.setGoal(null);
48 | };
49 |
50 | const explore = () => {
51 | const x =
52 | bot.entity.position.x +
53 | Math.floor(Math.random() * 20 + 10) * dx;
54 | const y =
55 | bot.entity.position.y +
56 | Math.floor(Math.random() * 20 + 10) * dy;
57 | const z =
58 | bot.entity.position.z +
59 | Math.floor(Math.random() * 20 + 10) * dz;
60 | let goal = new GoalNear(x, y, z);
61 | if (dy === 0) {
62 | goal = new GoalNearXZ(x, z);
63 | }
64 | bot.pathfinder.setGoal(goal);
65 |
66 | try {
67 | const result = callback();
68 | if (result) {
69 | cleanUp();
70 | bot.chat("Explore success.");
71 | resolve(result);
72 | }
73 | } catch (err) {
74 | cleanUp();
75 | reject(err);
76 | }
77 | };
78 |
79 | explorationInterval = setInterval(explore, 2000);
80 |
81 | maxTimeTimeout = setTimeout(() => {
82 | cleanUp();
83 | bot.chat("Max exploration time reached");
84 | resolve(null);
85 | }, maxTime * 1000);
86 | });
87 | }
88 |
--------------------------------------------------------------------------------
/Adam/control_primitives/givePlacedItemBack.js:
--------------------------------------------------------------------------------
1 | async function givePlacedItemBack(bot, name, position) {
2 | await bot.chat("/gamerule doTileDrops false");
3 | // iterate name and position
4 | const history = [];
5 | for (let i = 0; i < name.length; i++) {
6 | await givePlacedItemBackSingle(bot, name[i], position[i]);
7 | }
8 | await bot.chat("/gamerule doTileDrops true");
9 |
10 | async function givePlacedItemBackSingle(bot, name, position) {
11 | bot.chat(`/give bot ${name} 1`);
12 | const x = Math.floor(position.x);
13 | const y = Math.floor(position.y);
14 | const z = Math.floor(position.z);
15 | // loop through 125 blocks around the block
16 | const size = 3;
17 | for (let dx = -size; dx <= size; dx++) {
18 | for (let dy = -size; dy <= size; dy++) {
19 | for (let dz = -size; dz <= size; dz++) {
20 | const block = bot.blockAt(new Vec3(x + dx, y + dy, z + dz));
21 | if (
22 | block?.name === name &&
23 | !history.includes(block.position)
24 | ) {
25 | await bot.chat(
26 | `/setblock ${x + dx} ${y + dy} ${
27 | z + dz
28 | } air destroy`
29 | );
30 | history.push(block.position);
31 | await bot.waitForTicks(20);
32 | return;
33 | }
34 | }
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Adam/control_primitives/killMob.js:
--------------------------------------------------------------------------------
1 | async function killMob(bot, mobName, timeout = 300) {
2 | // return if mobName is not string
3 | if (typeof mobName !== "string") {
4 | throw new Error(`mobName for killMob must be a string`);
5 | }
6 | // return if timeout is not number
7 | if (typeof timeout !== "number") {
8 | throw new Error(`timeout for killMob must be a number`);
9 | }
10 |
11 | const weaponsForShooting = [
12 | "bow",
13 | "crossbow",
14 | "snowball",
15 | "ender_pearl",
16 | "egg",
17 | "splash_potion",
18 | "trident",
19 | ];
20 | const mainHandItem = bot.inventory.slots[bot.getEquipmentDestSlot("hand")];
21 |
22 | const entity = bot.nearestEntity(
23 | (entity) =>
24 | entity.name === mobName &&
25 | // kill mob distance should be slightly bigger than explore distance
26 | entity.position.distanceTo(bot.entity.position) < 48
27 | );
28 | if (!entity) {
29 | bot.chat(`No ${mobName} nearby, please explore first`);
30 | _killMobFailCount++;
31 | if (_killMobFailCount > 10) {
32 | throw new Error(
33 | `killMob failed too many times, make sure you explore before calling killMob`
34 | );
35 | }
36 | return;
37 | }
38 |
39 | let droppedItem;
40 | if (mainHandItem && weaponsForShooting.includes(mainHandItem.name)) {
41 | bot.hawkEye.autoAttack(entity, mainHandItem.name);
42 | droppedItem = await waitForMobShot(bot, entity, timeout);
43 | } else {
44 | await bot.pvp.attack(entity);
45 | droppedItem = await waitForMobRemoved(bot, entity, timeout);
46 | }
47 | if (droppedItem) {
48 | await bot.collectBlock.collect(droppedItem, { ignoreNoPath: true });
49 | }
50 | bot.save(`${mobName}_killed`);
51 | }
52 |
--------------------------------------------------------------------------------
/Adam/control_primitives/mineBlock.js:
--------------------------------------------------------------------------------
1 | async function mineBlock(bot, name, count = 1) {
2 | // return if name is not string
3 | if (typeof name !== "string") {
4 | throw new Error(`name for mineBlock must be a string`);
5 | }
6 | if (typeof count !== "number") {
7 | throw new Error(`count for mineBlock must be a number`);
8 | }
9 | const blockByName = mcData.blocksByName[name];
10 | if (!blockByName) {
11 | throw new Error(`No block named ${name}`);
12 | }
13 | const blocks = bot.findBlocks({
14 | matching: [blockByName.id],
15 | maxDistance: 32,
16 | count: 1024,
17 | });
18 | if (blocks.length === 0) {
19 | bot.chat(`No ${name} nearby, please explore first`);
20 | _mineBlockFailCount++;
21 | if (_mineBlockFailCount > 10) {
22 | throw new Error(
23 | "mineBlock failed too many times, make sure you explore before calling mineBlock"
24 | );
25 | }
26 | return;
27 | }
28 | const targets = [];
29 | for (let i = 0; i < blocks.length; i++) {
30 | targets.push(bot.blockAt(blocks[i]));
31 | }
32 | await bot.collectBlock.collect(targets, {
33 | ignoreNoPath: true,
34 | count: count,
35 | });
36 | bot.save(`${name}_mined`);
37 | }
38 |
--------------------------------------------------------------------------------
/Adam/control_primitives/placeItem.js:
--------------------------------------------------------------------------------
1 | async function placeItem(bot, name, position) {
2 | // return if name is not string
3 | if (typeof name !== "string") {
4 | throw new Error(`name for placeItem must be a string`);
5 | }
6 | // return if position is not Vec3
7 | if (!(position instanceof Vec3)) {
8 | throw new Error(`position for placeItem must be a Vec3`);
9 | }
10 | const itemByName = mcData.itemsByName[name];
11 | if (!itemByName) {
12 | throw new Error(`No item named ${name}`);
13 | }
14 | const item = bot.inventory.findInventoryItem(itemByName.id);
15 | if (!item) {
16 | bot.chat(`No ${name} in inventory`);
17 | return;
18 | }
19 | const item_count = item.count;
20 | // find a reference block
21 | const faceVectors = [
22 | new Vec3(0, 1, 0),
23 | new Vec3(0, -1, 0),
24 | new Vec3(1, 0, 0),
25 | new Vec3(-1, 0, 0),
26 | new Vec3(0, 0, 1),
27 | new Vec3(0, 0, -1),
28 | ];
29 | let referenceBlock = null;
30 | let faceVector = null;
31 | for (const vector of faceVectors) {
32 | const block = bot.blockAt(position.minus(vector));
33 | if (block?.name !== "air") {
34 | referenceBlock = block;
35 | faceVector = vector;
36 | bot.chat(`Placing ${name} on ${block.name} at ${block.position}`);
37 | break;
38 | }
39 | }
40 | if (!referenceBlock) {
41 | bot.chat(
42 | `No block to place ${name} on. You cannot place a floating block.`
43 | );
44 | _placeItemFailCount++;
45 | if (_placeItemFailCount > 10) {
46 | throw new Error(
47 | `placeItem failed too many times. You cannot place a floating block.`
48 | );
49 | }
50 | return;
51 | }
52 |
53 | // You must use try catch to placeBlock
54 | try {
55 | // You must first go to the block position you want to place
56 | await bot.pathfinder.goto(new GoalPlaceBlock(position, bot.world, {}));
57 | // You must equip the item right before calling placeBlock
58 | await bot.equip(item, "hand");
59 | await bot.placeBlock(referenceBlock, faceVector);
60 | bot.chat(`Placed ${name}`);
61 | bot.save(`${name}_placed`);
62 | } catch (err) {
63 | const item = bot.inventory.findInventoryItem(itemByName.id);
64 | if (item?.count === item_count) {
65 | bot.chat(
66 | `Error placing ${name}: ${err.message}, please find another position to place`
67 | );
68 | _placeItemFailCount++;
69 | if (_placeItemFailCount > 10) {
70 | throw new Error(
71 | `placeItem failed too many times, please find another position to place.`
72 | );
73 | }
74 | } else {
75 | bot.chat(`Placed ${name}`);
76 | bot.save(`${name}_placed`);
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/Adam/control_primitives/shoot.js:
--------------------------------------------------------------------------------
1 | // shoot 1 pig with a bow: shoot(bot, "bow", "pig");
2 | async function shoot(bot, weapon, target) {
3 | const validWeapons = [
4 | "bow",
5 | "crossbow",
6 | "snowball",
7 | "ender_pearl",
8 | "egg",
9 | "splash_potion",
10 | "trident",
11 | ];
12 | if (!validWeapons.includes(weapon)) {
13 | bot.chat(`${weapon} is not a valid weapon for shooting`);
14 | return;
15 | }
16 |
17 | const weaponItem = mcData.itemsByName[weapon];
18 | if (!bot.inventory.findInventoryItem(weaponItem.id, null)) {
19 | bot.chat(`No ${weapon} in inventory for shooting`);
20 | return;
21 | }
22 |
23 | const targetEntity = bot.nearestEntity(
24 | (entity) =>
25 | entity.name === target
26 | );
27 | if (!targetEntity) {
28 | bot.chat(`No ${target} nearby`);
29 | return;
30 | }
31 | bot.hawkEye.autoAttack(targetEntity, "bow");
32 | bot.on('auto_shot_stopped', (target) => {
33 | })
34 | }
35 |
--------------------------------------------------------------------------------
/Adam/control_primitives/smeltItem.js:
--------------------------------------------------------------------------------
1 | async function smeltItem(bot, itemName, fuelName, count = 1) {
2 | // return if itemName or fuelName is not string
3 | if (typeof itemName !== "string" || typeof fuelName !== "string") {
4 | throw new Error("itemName or fuelName for smeltItem must be a string");
5 | }
6 | // return if count is not a number
7 | if (typeof count !== "number") {
8 | throw new Error("count for smeltItem must be a number");
9 | }
10 | const item = mcData.itemsByName[itemName];
11 | const fuel = mcData.itemsByName[fuelName];
12 | if (!item) {
13 | throw new Error(`No item named ${itemName}`);
14 | }
15 | if (!fuel) {
16 | throw new Error(`No item named ${fuelName}`);
17 | }
18 | const furnaceBlock = bot.findBlock({
19 | matching: mcData.blocksByName.furnace.id,
20 | maxDistance: 32,
21 | });
22 | if (!furnaceBlock) {
23 | throw new Error("No furnace nearby");
24 | } else {
25 | await bot.pathfinder.goto(
26 | new GoalLookAtBlock(furnaceBlock.position, bot.world)
27 | );
28 | }
29 | const furnace = await bot.openFurnace(furnaceBlock);
30 | let success_count = 0;
31 | for (let i = 0; i < count; i++) {
32 | if (!bot.inventory.findInventoryItem(item.id, null)) {
33 | bot.chat(`No ${itemName} to smelt in inventory`);
34 | break;
35 | }
36 | if (furnace.fuelSeconds < 15 && furnace.fuelItem()?.name !== fuelName) {
37 | if (!bot.inventory.findInventoryItem(fuel.id, null)) {
38 | bot.chat(`No ${fuelName} as fuel in inventory`);
39 | break;
40 | }
41 | await furnace.putFuel(fuel.id, null, 1);
42 | await bot.waitForTicks(20);
43 | if (!furnace.fuel && furnace.fuelItem()?.name !== fuelName) {
44 | throw new Error(`${fuelName} is not a valid fuel`);
45 | }
46 | }
47 | await furnace.putInput(item.id, null, 1);
48 | await bot.waitForTicks(12 * 20);
49 | if (!furnace.outputItem()) {
50 | throw new Error(`${itemName} is not a valid input`);
51 | }
52 | await furnace.takeOutput();
53 | success_count++;
54 | }
55 | furnace.close();
56 | if (success_count > 0) bot.chat(`Smelted ${success_count} ${itemName}.`);
57 | else {
58 | bot.chat(
59 | `Failed to smelt ${itemName}, please check the fuel and input.`
60 | );
61 | _smeltItemFailCount++;
62 | if (_smeltItemFailCount > 10) {
63 | throw new Error(
64 | `smeltItem failed too many times, please check the fuel and input.`
65 | );
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Adam/control_primitives/useChest.js:
--------------------------------------------------------------------------------
1 | async function getItemFromChest(bot, chestPosition, itemsToGet) {
2 | // return if chestPosition is not Vec3
3 | if (!(chestPosition instanceof Vec3)) {
4 | bot.chat("chestPosition for getItemFromChest must be a Vec3");
5 | return;
6 | }
7 | await moveToChest(bot, chestPosition);
8 | const chestBlock = bot.blockAt(chestPosition);
9 | const chest = await bot.openContainer(chestBlock);
10 | for (const name in itemsToGet) {
11 | const itemByName = mcData.itemsByName[name];
12 | if (!itemByName) {
13 | bot.chat(`No item named ${name}`);
14 | continue;
15 | }
16 |
17 | const item = chest.findContainerItem(itemByName.id);
18 | if (!item) {
19 | bot.chat(`I don't see ${name} in this chest`);
20 | continue;
21 | }
22 | try {
23 | await chest.withdraw(item.type, null, itemsToGet[name]);
24 | } catch (err) {
25 | bot.chat(`Not enough ${name} in chest.`);
26 | }
27 | }
28 | await closeChest(bot, chestBlock);
29 | }
30 |
31 | async function depositItemIntoChest(bot, chestPosition, itemsToDeposit) {
32 | // return if chestPosition is not Vec3
33 | if (!(chestPosition instanceof Vec3)) {
34 | throw new Error(
35 | "chestPosition for depositItemIntoChest must be a Vec3"
36 | );
37 | }
38 | await moveToChest(bot, chestPosition);
39 | const chestBlock = bot.blockAt(chestPosition);
40 | const chest = await bot.openContainer(chestBlock);
41 | for (const name in itemsToDeposit) {
42 | const itemByName = mcData.itemsByName[name];
43 | if (!itemByName) {
44 | bot.chat(`No item named ${name}`);
45 | continue;
46 | }
47 | const item = bot.inventory.findInventoryItem(itemByName.id);
48 | if (!item) {
49 | bot.chat(`No ${name} in inventory`);
50 | continue;
51 | }
52 | try {
53 | await chest.deposit(item.type, null, itemsToDeposit[name]);
54 | } catch (err) {
55 | bot.chat(`Not enough ${name} in inventory.`);
56 | }
57 | }
58 | await closeChest(bot, chestBlock);
59 | }
60 |
61 | async function checkItemInsideChest(bot, chestPosition) {
62 | // return if chestPosition is not Vec3
63 | if (!(chestPosition instanceof Vec3)) {
64 | throw new Error(
65 | "chestPosition for depositItemIntoChest must be a Vec3"
66 | );
67 | }
68 | await moveToChest(bot, chestPosition);
69 | const chestBlock = bot.blockAt(chestPosition);
70 | await bot.openContainer(chestBlock);
71 | await closeChest(bot, chestBlock);
72 | }
73 |
74 | async function moveToChest(bot, chestPosition) {
75 | if (!(chestPosition instanceof Vec3)) {
76 | throw new Error(
77 | "chestPosition for depositItemIntoChest must be a Vec3"
78 | );
79 | }
80 | if (chestPosition.distanceTo(bot.entity.position) > 32) {
81 | bot.chat(
82 | `/tp ${chestPosition.x} ${chestPosition.y} ${chestPosition.z}`
83 | );
84 | await bot.waitForTicks(20);
85 | }
86 | const chestBlock = bot.blockAt(chestPosition);
87 | if (chestBlock.name !== "chest") {
88 | bot.emit("removeChest", chestPosition);
89 | throw new Error(
90 | `No chest at ${chestPosition}, it is ${chestBlock.name}`
91 | );
92 | }
93 | await bot.pathfinder.goto(
94 | new GoalLookAtBlock(chestBlock.position, bot.world, {})
95 | );
96 | return chestBlock;
97 | }
98 |
99 | async function listItemsInChest(bot, chestBlock) {
100 | const chest = await bot.openContainer(chestBlock);
101 | const items = chest.containerItems();
102 | if (items.length > 0) {
103 | const itemNames = items.reduce((acc, obj) => {
104 | if (acc[obj.name]) {
105 | acc[obj.name] += obj.count;
106 | } else {
107 | acc[obj.name] = obj.count;
108 | }
109 | return acc;
110 | }, {});
111 | bot.emit("closeChest", itemNames, chestBlock.position);
112 | } else {
113 | bot.emit("closeChest", {}, chestBlock.position);
114 | }
115 | return chest;
116 | }
117 |
118 | async function closeChest(bot, chestBlock) {
119 | try {
120 | const chest = await listItemsInChest(bot, chestBlock);
121 | await chest.close();
122 | } catch (err) {
123 | await bot.closeWindow(chestBlock);
124 | }
125 | }
126 |
127 | function itemByName(items, name) {
128 | for (let i = 0; i < items.length; ++i) {
129 | const item = items[i];
130 | if (item && item.name === name) return item;
131 | }
132 | return null;
133 | }
134 |
--------------------------------------------------------------------------------
/Adam/control_primitives/waitForMobRemoved.js:
--------------------------------------------------------------------------------
1 | function waitForMobRemoved(bot, entity, timeout = 300) {
2 | return new Promise((resolve, reject) => {
3 | let success = false;
4 | let droppedItem = null;
5 | // Set up timeout
6 | const timeoutId = setTimeout(() => {
7 | success = false;
8 | bot.pvp.stop();
9 | }, timeout * 1000);
10 |
11 | // Function to handle entityRemoved event
12 | function onEntityGone(e) {
13 | if (e === entity) {
14 | success = true;
15 | clearTimeout(timeoutId);
16 | bot.chat(`Killed ${entity.name}!`);
17 | bot.pvp.stop();
18 | }
19 | }
20 |
21 | function onItemDrop(item) {
22 | if (entity.position.distanceTo(item.position) <= 1) {
23 | droppedItem = item;
24 | }
25 | }
26 |
27 | function onStoppedAttacking() {
28 | clearTimeout(timeoutId);
29 | bot.removeListener("entityGone", onEntityGone);
30 | bot.removeListener("stoppedAttacking", onStoppedAttacking);
31 | bot.removeListener("itemDrop", onItemDrop);
32 | if (!success) reject(new Error(`Failed to kill ${entity.name}.`));
33 | else resolve(droppedItem);
34 | }
35 |
36 | // Listen for entityRemoved event
37 | bot.on("entityGone", onEntityGone);
38 | bot.on("stoppedAttacking", onStoppedAttacking);
39 | bot.on("itemDrop", onItemDrop);
40 | });
41 | }
42 |
43 |
44 | function waitForMobShot(bot, entity, timeout = 300) {
45 | return new Promise((resolve, reject) => {
46 | let success = false;
47 | let droppedItem = null;
48 | // Set up timeout
49 | const timeoutId = setTimeout(() => {
50 | success = false;
51 | bot.hawkEye.stop();
52 | }, timeout * 1000);
53 |
54 | // Function to handle entityRemoved event
55 | function onEntityGone(e) {
56 | if (e === entity) {
57 | success = true;
58 | clearTimeout(timeoutId);
59 | bot.chat(`Shot ${entity.name}!`);
60 | bot.hawkEye.stop();
61 | }
62 | }
63 |
64 | function onItemDrop(item) {
65 | if (entity.position.distanceTo(item.position) <= 1) {
66 | droppedItem = item;
67 | }
68 | }
69 |
70 | function onAutoShotStopped() {
71 | clearTimeout(timeoutId);
72 | bot.removeListener("entityGone", onEntityGone);
73 | bot.removeListener("auto_shot_stopped", onAutoShotStopped);
74 | bot.removeListener("itemDrop", onItemDrop);
75 | if (!success) reject(new Error(`Failed to shoot ${entity.name}.`));
76 | else resolve(droppedItem);
77 | }
78 |
79 | // Listen for entityRemoved event
80 | bot.on("entityGone", onEntityGone);
81 | bot.on("auto_shot_stopped", onAutoShotStopped);
82 | bot.on("itemDrop", onItemDrop);
83 | });
84 | }
85 |
--------------------------------------------------------------------------------
/Adam/control_primitives_context/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 4
3 | }
4 |
--------------------------------------------------------------------------------
/Adam/control_primitives_context/__init__.py:
--------------------------------------------------------------------------------
1 | import pkg_resources
2 | import os
3 | import voyager.utils as U
4 |
5 |
6 | def load_control_primitives_context(primitive_names=None):
7 | package_path = pkg_resources.resource_filename("voyager", "")
8 | if primitive_names is None:
9 | primitive_names = [
10 | primitive[:-3]
11 | for primitive in os.listdir(f"{package_path}/control_primitives_context")
12 | if primitive.endswith(".js")
13 | ]
14 | primitives = [
15 | U.load_text(f"{package_path}/control_primitives_context/{primitive_name}.js")
16 | for primitive_name in primitive_names
17 | ]
18 | return primitives
19 |
--------------------------------------------------------------------------------
/Adam/control_primitives_context/craftItem.js:
--------------------------------------------------------------------------------
1 | // Craft 8 oak_planks from 2 oak_log (do the recipe 2 times): craftItem(bot, "oak_planks", 2);
2 | // You must place a crafting table before calling this function
3 | async function craftItem(bot, name, count = 1) {
4 | const item = mcData.itemsByName[name];
5 | const craftingTable = bot.findBlock({
6 | matching: mcData.blocksByName.crafting_table.id,
7 | maxDistance: 32,
8 | });
9 | await bot.pathfinder.goto(
10 | new GoalLookAtBlock(craftingTable.position, bot.world)
11 | );
12 | const recipe = bot.recipesFor(item.id, null, 1, craftingTable)[0];
13 | await bot.craft(recipe, count, craftingTable);
14 | }
15 |
--------------------------------------------------------------------------------
/Adam/control_primitives_context/exploreUntil.js:
--------------------------------------------------------------------------------
1 | /*
2 | Explore until find an iron_ore, use Vec3(0, -1, 0) because iron ores are usually underground
3 | await exploreUntil(bot, new Vec3(0, -1, 0), 60, () => {
4 | const iron_ore = bot.findBlock({
5 | matching: mcData.blocksByName["iron_ore"].id,
6 | maxDistance: 32,
7 | });
8 | return iron_ore;
9 | });
10 |
11 | Explore until find a pig, use Vec3(1, 0, 1) because pigs are usually on the surface
12 | let pig = await exploreUntil(bot, new Vec3(1, 0, 1), 60, () => {
13 | const pig = bot.nearestEntity((entity) => {
14 | return (
15 | entity.name === "pig" &&
16 | entity.position.distanceTo(bot.entity.position) < 32
17 | );
18 | });
19 | return pig;
20 | });
21 | */
22 | async function exploreUntil(bot, direction, maxTime = 60, callback) {
23 | /*
24 | Implementation of this function is omitted.
25 | direction: Vec3, can only contain value of -1, 0 or 1
26 | maxTime: number, the max time for exploration
27 | callback: function, early stop condition, will be called each second, exploration will stop if return value is not null
28 |
29 | Return: null if explore timeout, otherwise return the return value of callback
30 | */
31 | }
32 |
--------------------------------------------------------------------------------
/Adam/control_primitives_context/killMob.js:
--------------------------------------------------------------------------------
1 | // Kill a pig and collect the dropped item: killMob(bot, "pig", 300);
2 | async function killMob(bot, mobName, timeout = 300) {
3 | const entity = bot.nearestEntity(
4 | (entity) =>
5 | entity.name === mobName &&
6 | entity.position.distanceTo(bot.entity.position) < 32
7 | );
8 | await bot.pvp.attack(entity);
9 | await bot.pathfinder.goto(
10 | new GoalBlock(entity.position.x, entity.position.y, entity.position.z)
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/Adam/control_primitives_context/mineBlock.js:
--------------------------------------------------------------------------------
1 | // Mine 3 cobblestone: mineBlock(bot, "stone", 3);
2 | async function mineBlock(bot, name, count = 1) {
3 | const blocks = bot.findBlocks({
4 | matching: (block) => {
5 | return block.name === name;
6 | },
7 | maxDistance: 32,
8 | count: count,
9 | });
10 | const targets = [];
11 | for (let i = 0; i < Math.min(blocks.length, count); i++) {
12 | targets.push(bot.blockAt(blocks[i]));
13 | }
14 | await bot.collectBlock.collect(targets, { ignoreNoPath: true });
15 | }
16 |
--------------------------------------------------------------------------------
/Adam/control_primitives_context/mineflayer.js:
--------------------------------------------------------------------------------
1 | await bot.pathfinder.goto(goal); // A very useful function. This function may change your main-hand equipment.
2 | // Following are some Goals you can use:
3 | new GoalNear(x, y, z, range); // Move the bot to a block within the specified range of the specified block. `x`, `y`, `z`, and `range` are `number`
4 | new GoalXZ(x, z); // Useful for long-range goals that don't have a specific Y level. `x` and `z` are `number`
5 | new GoalGetToBlock(x, y, z); // Not get into the block, but get directly adjacent to it. Useful for fishing, farming, filling bucket, and beds. `x`, `y`, and `z` are `number`
6 | new GoalFollow(entity, range); // Follow the specified entity within the specified range. `entity` is `Entity`, `range` is `number`
7 | new GoalPlaceBlock(position, bot.world, {}); // Position the bot in order to place a block. `position` is `Vec3`
8 | new GoalLookAtBlock(position, bot.world, {}); // Path into a position where a blockface of the block at position is visible. `position` is `Vec3`
9 |
10 | // These are other Mineflayer functions you can use:
11 | bot.isABed(bedBlock); // Return true if `bedBlock` is a bed
12 | bot.blockAt(position); // Return the block at `position`. `position` is `Vec3`
13 |
14 | // These are other Mineflayer async functions you can use:
15 | await bot.equip(item, destination); // Equip the item in the specified destination. `item` is `Item`, `destination` can only be "hand", "head", "torso", "legs", "feet", "off-hand"
16 | await bot.consume(); // Consume the item in the bot's hand. You must equip the item to consume first. Useful for eating food, drinking potions, etc.
17 | await bot.fish(); // Let bot fish. Before calling this function, you must first get to a water block and then equip a fishing rod. The bot will automatically stop fishing when it catches a fish
18 | await bot.sleep(bedBlock); // Sleep until sunrise. You must get to a bed block first
19 | await bot.activateBlock(block); // This is the same as right-clicking a block in the game. Useful for buttons, doors, etc. You must get to the block first
20 | await bot.lookAt(position); // Look at the specified position. You must go near the position before you look at it. To fill bucket with water, you must lookAt first. `position` is `Vec3`
21 | await bot.activateItem(); // This is the same as right-clicking to use the item in the bot's hand. Useful for using buckets, etc. You must equip the item to activate first
22 | await bot.useOn(entity); // This is the same as right-clicking an entity in the game. Useful for shearing sheep, equipping harnesses, etc. You must get to the entity first
23 |
--------------------------------------------------------------------------------
/Adam/control_primitives_context/placeItem.js:
--------------------------------------------------------------------------------
1 | // Place a crafting_table near the player, Vec3(1, 0, 0) is just an example, you shouldn't always use that: placeItem(bot, "crafting_table", bot.entity.position.offset(1, 0, 0));
2 | async function placeItem(bot, name, position) {
3 | const item = bot.inventory.findInventoryItem(mcData.itemsByName[name].id);
4 | // find a reference block
5 | const faceVectors = [
6 | new Vec3(0, 1, 0),
7 | new Vec3(0, -1, 0),
8 | new Vec3(1, 0, 0),
9 | new Vec3(-1, 0, 0),
10 | new Vec3(0, 0, 1),
11 | new Vec3(0, 0, -1),
12 | ];
13 | let referenceBlock = null;
14 | let faceVector = null;
15 | for (const vector of faceVectors) {
16 | const block = bot.blockAt(position.minus(vector));
17 | if (block?.name !== "air") {
18 | referenceBlock = block;
19 | faceVector = vector;
20 | break;
21 | }
22 | }
23 | // You must first go to the block position you want to place
24 | await bot.pathfinder.goto(new GoalPlaceBlock(position, bot.world, {}));
25 | // You must equip the item right before calling placeBlock
26 | await bot.equip(item, "hand");
27 | await bot.placeBlock(referenceBlock, faceVector);
28 | }
29 |
--------------------------------------------------------------------------------
/Adam/control_primitives_context/smeltItem.js:
--------------------------------------------------------------------------------
1 | // Smelt 1 raw_iron into 1 iron_ingot using 1 oak_planks as fuel: smeltItem(bot, "raw_iron", "oak_planks");
2 | // You must place a furnace before calling this function
3 | async function smeltItem(bot, itemName, fuelName, count = 1) {
4 | const item = mcData.itemsByName[itemName];
5 | const fuel = mcData.itemsByName[fuelName];
6 | const furnaceBlock = bot.findBlock({
7 | matching: mcData.blocksByName.furnace.id,
8 | maxDistance: 32,
9 | });
10 | await bot.pathfinder.goto(
11 | new GoalLookAtBlock(furnaceBlock.position, bot.world)
12 | );
13 | const furnace = await bot.openFurnace(furnaceBlock);
14 | for (let i = 0; i < count; i++) {
15 | await furnace.putFuel(fuel.id, null, 1);
16 | await furnace.putInput(item.id, null, 1);
17 | // Wait 12 seconds for the furnace to smelt the item
18 | await bot.waitForTicks(12 * 20);
19 | await furnace.takeOutput();
20 | }
21 | await furnace.close();
22 | }
23 |
--------------------------------------------------------------------------------
/Adam/control_primitives_context/useChest.js:
--------------------------------------------------------------------------------
1 | // Get a torch from chest at (30, 65, 100): getItemFromChest(bot, new Vec3(30, 65, 100), {"torch": 1});
2 | // This function will work no matter how far the bot is from the chest.
3 | async function getItemFromChest(bot, chestPosition, itemsToGet) {
4 | await moveToChest(bot, chestPosition);
5 | const chestBlock = bot.blockAt(chestPosition);
6 | const chest = await bot.openContainer(chestBlock);
7 | for (const name in itemsToGet) {
8 | const itemByName = mcData.itemsByName[name];
9 | const item = chest.findContainerItem(itemByName.id);
10 | await chest.withdraw(item.type, null, itemsToGet[name]);
11 | }
12 | await closeChest(bot, chestBlock);
13 | }
14 | // Deposit a torch into chest at (30, 65, 100): depositItemIntoChest(bot, new Vec3(30, 65, 100), {"torch": 1});
15 | // This function will work no matter how far the bot is from the chest.
16 | async function depositItemIntoChest(bot, chestPosition, itemsToDeposit) {
17 | await moveToChest(bot, chestPosition);
18 | const chestBlock = bot.blockAt(chestPosition);
19 | const chest = await bot.openContainer(chestBlock);
20 | for (const name in itemsToDeposit) {
21 | const itemByName = mcData.itemsByName[name];
22 | const item = bot.inventory.findInventoryItem(itemByName.id);
23 | await chest.deposit(item.type, null, itemsToDeposit[name]);
24 | }
25 | await closeChest(bot, chestBlock);
26 | }
27 | // Check the items inside the chest at (30, 65, 100): checkItemInsideChest(bot, new Vec3(30, 65, 100));
28 | // You only need to call this function once without any action to finish task of checking items inside the chest.
29 | async function checkItemInsideChest(bot, chestPosition) {
30 | await moveToChest(bot, chestPosition);
31 | const chestBlock = bot.blockAt(chestPosition);
32 | await bot.openContainer(chestBlock);
33 | // You must close the chest after opening it if you are asked to open a chest
34 | await closeChest(bot, chestBlock);
35 | }
36 |
--------------------------------------------------------------------------------
/Adam/infer_API.py:
--------------------------------------------------------------------------------
1 | import openai
2 | import requests
3 |
4 |
5 | def get_response(prompt='', model_name='gpt-4-turbo-preview'):
6 | response = openai.chat.completions.create(
7 | model=model_name,
8 | messages=[
9 | {'role': 'user', 'content': prompt
10 | }
11 | ],
12 | temperature=0.3,
13 | )
14 | return response.choices[0].message.content.strip()
15 |
16 |
17 | def get_local_response(prompt='', local_llm_port=6000):
18 | url = 'http://127.0.0.1:' + str(local_llm_port) + '/send'
19 | data = {'text': prompt}
20 | response = requests.post(url, json=data)
21 | if response.status_code == 200:
22 | response_data = response.json()
23 | print(response_data.get('response', '').strip())
24 | return response_data.get('response', '').strip()
25 | else:
26 | return f'Error: {response.status_code}'
27 |
--------------------------------------------------------------------------------
/Adam/module_utils.py:
--------------------------------------------------------------------------------
1 | import json
2 | from datetime import datetime
3 | import Adam.util_info
4 | import utils as U
5 | from functools import cmp_to_key
6 |
7 |
8 | def compare_keys(key1, key2):
9 | if len(key1) < len(key2):
10 | return -1
11 | elif len(key1) > len(key2):
12 | return 1
13 | else:
14 | if key1 < key2:
15 | return -1
16 | elif key1 > key2:
17 | return 1
18 | else:
19 | return 0
20 |
21 |
22 | key_cmp_func = cmp_to_key(compare_keys)
23 |
24 |
25 | def generate_next_key(current_key):
26 | if current_key[-1] != 'z':
27 | return current_key[:-1] + chr(ord(current_key[-1]) + 1)
28 | else:
29 | if current_key == 'z':
30 | return 'aa'
31 | else:
32 | return generate_next_key(current_key[:-1]) + 'a'
33 |
34 |
35 | def rename_item(item: str):
36 | if 'log' in item:
37 | return 'log'
38 | elif 'planks' in item:
39 | return 'planks'
40 | elif 'fence_gate' in item:
41 | return 'fence_gate'
42 | elif 'fence' in item:
43 | return 'fence'
44 | else:
45 | return item
46 |
47 |
48 | def rename_item_rev(item: str):
49 | if 'log' in item:
50 | return 'oak_log'
51 | elif 'planks' in item:
52 | return 'oak_planks'
53 | elif 'fence_gate' in item:
54 | return 'oak_fence_gate'
55 | elif 'fence' in item:
56 | return 'oak_fence'
57 | else:
58 | return item
59 |
60 |
61 | def translate_item_name_to_letter(name: str):
62 | return Adam.util_info.material_names_rev_dict[rename_item(name)]
63 |
64 |
65 | def translate_item_name_list_to_letter(name_list: list):
66 | return [translate_item_name_to_letter(item) for item in name_list]
67 |
68 |
69 | def translate_item_letter_to_name(letter: str):
70 | return Adam.util_info.material_names_dict[letter]
71 |
72 |
73 | def translate_action_name_to_letter(name: str):
74 | return Adam.util_info.action_names_rev_dict[name]
75 |
76 |
77 | def translate_action_letter_to_name(letter: str):
78 | if letter[:4] == 'move':
79 | return letter
80 | return Adam.util_info.action_names_dict[letter]
81 |
82 |
83 | def check_in_material(added_items: list, effect: str):
84 | for added_item in added_items:
85 | if translate_item_letter_to_name(effect) == rename_item(added_item):
86 | return True
87 | return False
88 |
89 |
90 | def check_len_valid(materials: list):
91 | for item in materials:
92 | if len(item) > 2:
93 | return False
94 | return True
95 |
96 |
97 | def get_inventory_number(inventory: dict, material: str):
98 | material_name = rename_item_rev(translate_item_letter_to_name(material))
99 | if material_name in ['oak_log', 'oak_planks', 'stick', 'cobblestone', 'raw_iron', 'iron_ingot', 'diamond',
100 | 'raw_gold', 'gold_ingot']:
101 | inventory[material_name] = 32
102 | else:
103 | inventory[material_name] = 1
104 | return inventory
105 |
106 |
107 | def get_item_changes(start_item: dict, end_item: dict):
108 | consumed_items = []
109 | added_items = []
110 |
111 | for item, quantity in start_item.items():
112 | if item not in end_item or end_item[item] < quantity:
113 | consumed_items.append(item)
114 |
115 | for item, quantity in end_item.items():
116 | if item not in start_item or start_item[item] < quantity:
117 | added_items.append(item)
118 |
119 | return consumed_items, added_items
120 |
121 |
122 | def recorder(start_item: dict, end_item: dict, consumed_items: list, added_items: list, action_type: str,
123 | file_path: str):
124 | log_json_path = U.f_join(file_path, "log_data", action_type + ".json")
125 | log_dict = {
126 | 'Start item': start_item,
127 | 'End item': end_item,
128 | 'Action type': action_type,
129 | 'Consumed items': consumed_items,
130 | 'Added items': added_items,
131 | }
132 |
133 | try:
134 | with open(log_json_path, 'r') as file:
135 | try:
136 | logs = json.load(file)
137 | except json.JSONDecodeError:
138 | logs = []
139 | except FileNotFoundError:
140 | logs = []
141 | logs.append(log_dict)
142 | with open(log_json_path, 'w') as file:
143 | json.dump(logs, file, indent=4)
144 |
145 |
146 | def get_time():
147 | now = datetime.now()
148 | return now.strftime("%Y-%m-%d-%H-%M-%S")
149 |
--------------------------------------------------------------------------------
/Adam/skill_loader.py:
--------------------------------------------------------------------------------
1 | import os
2 | import utils as U
3 | from javascript import require
4 |
5 |
6 | def _skill_loader(skill: str):
7 | file_path = os.path.abspath(os.path.dirname(__file__))
8 | file_path = U.f_join(file_path, 'ActionLib', skill + '.js')
9 | with open(file_path, 'r', encoding='utf-8') as file:
10 | content = file.read()
11 | return content
12 |
13 |
14 | def process_message(message):
15 | retry = 3
16 | error = None
17 | while retry > 0:
18 | try:
19 | babel = require("@babel/core")
20 | babel_generator = require("@babel/generator").default
21 | code = message
22 | parsed = babel.parse(code)
23 | functions = []
24 | assert len(list(parsed.program.body)) > 0, "No functions found"
25 | for i, node in enumerate(parsed.program.body):
26 | if node.type != "FunctionDeclaration":
27 | continue
28 | node_type = (
29 | "AsyncFunctionDeclaration"
30 | if node["async"]
31 | else "FunctionDeclaration"
32 | )
33 | functions.append(
34 | {
35 | "name": node.id.name,
36 | "type": node_type,
37 | "body": babel_generator(node).code,
38 | "params": list(node["params"]),
39 | }
40 | )
41 | # find the last async function
42 | main_function = None
43 | for function in reversed(functions):
44 | if function["type"] == "AsyncFunctionDeclaration":
45 | main_function = function
46 | break
47 | assert (
48 | main_function is not None
49 | ), "No async function found. Your main function must be async."
50 | assert (
51 | len(main_function["params"]) == 1
52 | and main_function["params"][0].name == "bot"
53 | ), f"Main function {main_function['name']} must take a single argument named 'bot'"
54 | program_code = "\n\n".join(function["body"] for function in functions)
55 | exec_code = f"await {main_function['name']}(bot);"
56 | return {
57 | "program_code": program_code,
58 | "program_name": main_function["name"],
59 | "exec_code": exec_code,
60 | }
61 | except Exception as e:
62 | retry -= 1
63 | error = e
64 | return f"Error parsing action response (before program execution): {error}"
65 |
66 |
67 | def load_control_primitives(primitive_names=None):
68 | file_path = os.path.abspath(os.path.dirname(__file__))
69 | if primitive_names is None:
70 | primitive_names = [
71 | primitives[:-3]
72 | for primitives in os.listdir(f"{file_path}/control_primitives")
73 | if primitives.endswith(".js")
74 | ]
75 | primitives = [
76 | U.load_text(f"{file_path}/control_primitives/{primitive_name}.js")
77 | for primitive_name in primitive_names
78 | ]
79 | return primitives
80 |
81 |
82 | def skill_loader(skill: str):
83 | parsed_result = process_message(_skill_loader(skill))
84 | return "\n".join(load_control_primitives()) + "\n" + parsed_result["program_code"] + "\n" + \
85 | parsed_result["exec_code"]
86 |
87 | #print(skill_loader('mineCoalOre'))
88 |
--------------------------------------------------------------------------------
/Adam/util_info.py:
--------------------------------------------------------------------------------
1 | action_names_dict = {
2 | "A": "gatherWoodLog",
3 | "B": "craftPlanks",
4 | "C": "craftCraftingTable",
5 | "D": "craftSticks",
6 | "E": "craftFence",
7 | "F": "craftFenceGate",
8 | "G": "craftWoodenAxe",
9 | "H": "craftWoodenHoe",
10 | "I": "craftWoodenShovel",
11 | "J": "craftWoodenSword",
12 | "K": "craftWoodenPickaxe",
13 | "L": "gatherCoalOre",
14 | "M": "gatherStone",
15 | "N": "craftStoneAxe",
16 | "O": "craftStoneHoe",
17 | "P": "craftStoneShovel",
18 | "Q": "craftStoneSword",
19 | "R": "craftStonePickaxe",
20 | "S": "craftFurnace",
21 | "T": "mineIronOre",
22 | "U": "smeltRawIron",
23 | "V": "craftIronAxe",
24 | "W": "craftIronHoe",
25 | "X": "craftIronShovel",
26 | "Y": "craftIronSword",
27 | "Z": "craftIronPickaxe",
28 | "AA": "mineGoldOre",
29 | "AB": "mineDiamondOre",
30 | "AC": "craftDiamondAxe",
31 | "AD": "craftDiamondHoe",
32 | "AE": "craftDiamondPickaxe",
33 | "AF": "craftDiamondShovel",
34 | "AG": "craftDiamondSword",
35 | "AH": "gatherDirt",
36 | "AI": "gatherSand",
37 | "AJ": "smeltRawGold",
38 | "AK": "craftGoldenAxe",
39 | "AL": "craftGoldenHoe",
40 | "AM": "craftGoldenPickaxe",
41 | "AN": "craftGoldenShovel",
42 | "AO": "craftGoldenSword"
43 | }
44 |
45 | action_names_rev_dict = {
46 | "gatherWoodLog": "A",
47 | "craftPlanks": "B",
48 | "craftCraftingTable": "C",
49 | "craftSticks": "D",
50 | "craftFence": "E",
51 | "craftFenceGate": "F",
52 | "craftWoodenAxe": "G",
53 | "craftWoodenHoe": "H",
54 | "craftWoodenShovel": "I",
55 | "craftWoodenSword": "J",
56 | "craftWoodenPickaxe": "K",
57 | "gatherCoalOre": "L",
58 | "gatherStone": "M",
59 | "craftStoneAxe": "N",
60 | "craftStoneHoe": "O",
61 | "craftStoneShovel": "P",
62 | "craftStoneSword": "Q",
63 | "craftStonePickaxe": "R",
64 | "craftFurnace": "S",
65 | "mineIronOre": "T",
66 | "smeltRawIron": "U",
67 | "craftIronAxe": "V",
68 | "craftIronHoe": "W",
69 | "craftIronShovel": "X",
70 | "craftIronSword": "Y",
71 | "craftIronPickaxe": "Z",
72 | "mineGoldOre": "AA",
73 | "mineDiamondOre": "AB",
74 | "craftDiamondAxe": "AC",
75 | "craftDiamondHoe": "AD",
76 | "craftDiamondPickaxe": "AE",
77 | "craftDiamondShovel": "AF",
78 | "craftDiamondSword": "AG",
79 | "gatherDirt": "AH",
80 | "gatherSand": "AI",
81 | "smeltRawGold": "AJ",
82 | "craftGoldenAxe": "AK",
83 | "craftGoldenHoe": "AL",
84 | "craftGoldenPickaxe": "AM",
85 | "craftGoldenShovel": "AN",
86 | "craftGoldenSword": "AO"
87 | }
88 |
89 | material_names_dict = {
90 | 'a': 'log',
91 | 'b': 'planks',
92 | 'c': 'crafting_table',
93 | 'd': 'stick',
94 | 'e': 'wooden_pickaxe',
95 | 'f': 'cobblestone',
96 | 'g': 'stone_pickaxe',
97 | 'h': 'raw_iron',
98 | 'i': 'furnace',
99 | 'j': 'iron_ingot',
100 | 'k': 'iron_pickaxe',
101 | 'l': 'diamond',
102 | 'm': 'diamond_axe',
103 | 'n': 'diamond_hoe',
104 | 'o': 'diamond_pickaxe',
105 | 'p': 'diamond_shovel',
106 | 'q': 'diamond_sword',
107 | 'r': 'fence',
108 | 's': 'fence_gate',
109 | 't': 'golden_axe',
110 | 'u': 'golden_hoe',
111 | 'v': 'golden_pickaxe',
112 | 'w': 'golden_shovel',
113 | 'x': 'golden_sword',
114 | 'y': 'iron_axe',
115 | 'z': 'iron_hoe',
116 | 'aa': 'iron_shovel',
117 | 'ab': 'iron_sword',
118 | 'ac': 'stone_axe',
119 | 'ad': 'stone_hoe',
120 | 'ae': 'stone_shovel',
121 | 'af': 'stone_sword',
122 | 'ag': 'wooden_axe',
123 | 'ah': 'wooden_hoe',
124 | 'ai': 'wooden_shovel',
125 | 'aj': 'wooden_sword',
126 | 'ak': 'coal',
127 | 'al': 'dirt',
128 | 'am': 'raw_gold',
129 | 'an': 'sand',
130 | 'ao': 'gold_ingot'
131 | }
132 |
133 | material_names_rev_dict = {
134 | 'log': 'a',
135 | 'planks': 'b',
136 | 'crafting_table': 'c',
137 | 'stick': 'd',
138 | 'wooden_pickaxe': 'e',
139 | 'cobblestone': 'f',
140 | 'stone_pickaxe': 'g',
141 | 'raw_iron': 'h',
142 | 'furnace': 'i',
143 | 'iron_ingot': 'j',
144 | 'iron_pickaxe': 'k',
145 | 'diamond': 'l',
146 | 'diamond_axe': 'm',
147 | 'diamond_hoe': 'n',
148 | 'diamond_pickaxe': 'o',
149 | 'diamond_shovel': 'p',
150 | 'diamond_sword': 'q',
151 | 'fence': 'r',
152 | 'fence_gate': 's',
153 | 'golden_axe': 't',
154 | 'golden_hoe': 'u',
155 | 'golden_pickaxe': 'v',
156 | 'golden_shovel': 'w',
157 | 'golden_sword': 'x',
158 | 'iron_axe': 'y',
159 | 'iron_hoe': 'z',
160 | 'iron_shovel': 'aa',
161 | 'iron_sword': 'ab',
162 | 'stone_axe': 'ac',
163 | 'stone_hoe': 'ad',
164 | 'stone_shovel': 'ae',
165 | 'stone_sword': 'af',
166 | 'wooden_axe': 'ag',
167 | 'wooden_hoe': 'ah',
168 | 'wooden_shovel': 'ai',
169 | 'wooden_sword': 'aj',
170 | 'coal': 'ak',
171 | 'dirt': 'al',
172 | 'raw_gold': 'am',
173 | 'sand': 'an',
174 | 'gold_ingot': 'ao'
175 | }
176 |
177 | unlock = {
178 | "a": [
179 | "B"
180 | ],
181 | "b": [
182 | "C",
183 | "D"
184 | ],
185 | "c": [
186 | "E",
187 | "F",
188 | "G",
189 | "H",
190 | "I",
191 | "J",
192 | "K"
193 | ],
194 | "d": [],
195 | "e": [
196 | "L",
197 | "M"
198 | ],
199 | "f": [
200 | "N",
201 | "O",
202 | "P",
203 | "Q",
204 | "R",
205 | "S"
206 | ],
207 | "g": [
208 | "T"
209 | ],
210 | "h": [
211 | "U"
212 | ],
213 | "i": [],
214 | "j": [
215 | "V",
216 | "W",
217 | "X",
218 | "Y",
219 | "Z"
220 | ],
221 | "k": [
222 | "AA",
223 | "AB"
224 | ],
225 | "l": [
226 | "AC",
227 | "AD",
228 | "AE",
229 | "AF",
230 | "AG"
231 | ],
232 | "m": [],
233 | "n": [],
234 | "o": [],
235 | "p": [],
236 | "q": [],
237 | "r": [],
238 | "s": [],
239 | "t": [],
240 | "u": [],
241 | "v": [],
242 | "w": [],
243 | "x": [],
244 | "y": [],
245 | "z": [],
246 | "aa": [],
247 | "ab": [],
248 | "ac": [],
249 | "ad": [],
250 | "ae": [],
251 | "af": [],
252 | "ag": [],
253 | "ah": [],
254 | "ai": [
255 | "AH",
256 | "AI"
257 | ],
258 | "aj": [],
259 | "ak": [],
260 | "al": [],
261 | "am": [
262 | "AJ"
263 | ],
264 | "an": [],
265 | "ao": [
266 | "AK",
267 | "AL",
268 | "AM",
269 | "AN",
270 | "AO"
271 | ]
272 | }
273 |
--------------------------------------------------------------------------------
/Adam/visual_API.py:
--------------------------------------------------------------------------------
1 | import os
2 | import time
3 | from selenium import webdriver
4 | import utils as U
5 |
6 | class VisualAPI:
7 | def __init__(self):
8 | self.driver = webdriver.Chrome()
9 |
10 | self.driver.set_window_size(320, 512)
11 |
12 | def run(self):
13 | self.driver.get("http://localhost:9000")
14 | IMAGE_DIR = 'Adam/game_image'
15 | U.f_mkdir(IMAGE_DIR)
16 | if not os.path.exists(IMAGE_DIR):
17 | os.makedirs(IMAGE_DIR)
18 | print('Visual API Ready')
19 | while True:
20 | screenshot_path = os.path.join(IMAGE_DIR, 'tmp.png')
21 | self.driver.save_screenshot(screenshot_path)
22 | time.sleep(10)
23 |
24 | def stop(self):
25 | self.driver.quit()
26 |
27 |
28 | module = VisualAPI()
29 | module.run()
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ADAM: An Embodied Causal Agent in Open-World Environments
2 |
3 |
4 |
5 | [[Website]](https://opencausalab.github.io/ADAM)
6 | [[Arxiv]](https://arxiv.org/abs/2410.22194)
7 | [[PDF]](https://arxiv.org/pdf/2410.22194)
8 |
9 | [](https://github.com/OpenCausaLab/ADAM)
10 | [](https://github.com/OpenCausaLab/ADAM)
11 | ______________________________________________________________________
12 |
13 |
14 |
15 | We introduce ADAM, An emboDied causal Agent in Minecraft, that can autonomously navigate the open world, perceive multimodal contexts, learn causal world knowledge, and tackle complex tasks through lifelong learning.
16 | ADAM is empowered by four key components: 1) an interaction module, enabling the agent to execute actions while documenting the interaction processes; 2) a causal model module, tasked with constructing an ever-growing causal graph (i.e., technology tree) from scratch, which enhances interpretability and diminishes reliance on prior knowledge; 3) a controller module, comprising a planner, an actor, and a memory pool, which uses the learned causal graph to accomplish tasks; 4) a perception module, powered by multimodal large language models, which enables ADAM to perceive like a human player. Our project page is at [https://opencausalab.github.io/ADAM](https://opencausalab.github.io/ADAM)
17 |
18 | 
19 |
20 | Extensive experiments show that ADAM accurately constructs an almost perfect causal graph, enabling effective task decomposition and execution with increased efficiency and strong interpretability. Additionally, in modified Minecraft scenarios where no prior knowledge is available, ADAM maintains its performance and shows remarkable robustness and generalization capability.
21 |
22 | We provide the implementation of ADAM in this repo. We employ [Mineflayer](https://github.com/PrismarineJS/mineflayer),
23 | a JavaScript-based framework designed for integration with the commercial Minecraft. For visual
24 | processing, we utilize [prismarine-viewer](https://github.com/PrismarineJS/prismarine-viewer), an API for visualizing
25 | Minecraft content. The encapsulation of the Mineflayer uses the implementation
26 | in [VOYAGER](https://github.com/MineDojo/Voyager).
27 |
28 | # Installation
29 |
30 | ADAM requires Python ≥ 3.9 and Node.js ≥ 16.13.0. Our experiment tested the Minecraft environment on the **Windows**
31 | platform. The Azure Cloud Service is also available in the implementation.
32 |
33 | ## Python Install
34 |
35 | ```
36 | git clone https://github.com/OpenCausaLab/ADAM
37 | cd ADAM
38 | pip install -e .
39 | ```
40 |
41 | ## Node.js Install
42 |
43 | In addition to the Python dependencies, you need to install the following Node.js packages:
44 |
45 | ```
46 | cd env/mineflayer
47 | npm install -g npx
48 | npm install
49 | cd mineflayer-collectblock
50 | npx tsc
51 | cd ..
52 | npm install
53 | ```
54 |
55 | For users from mainland China, we recommend the following installation method:
56 |
57 | ```
58 | cd env/mineflayer
59 | npm config set registry https://registry.npmmirror.com
60 | npm install -g npx
61 | npm install
62 | cd mineflayer-collectblock
63 | npx tsc
64 | cd ..
65 | npm install
66 | ```
67 |
68 | ## Minecraft Instance and Fabric Mods Install
69 |
70 | The tutorial is from [VOYAGER](https://github.com/MineDojo/Voyager).
71 |
72 | ### Minecraft Instance Install
73 |
74 | Follow the instructions in [Minecraft Login Tutorial](installation/minecraft_instance_install.md) to set up your
75 | Minecraft Instance.
76 |
77 | ### Fabric Mods Install
78 |
79 | Follow the instructions in [Fabric Mods Install](installation/fabric_mods_install.md) to install the mods.
80 |
81 | # Getting Started
82 |
83 | You can run ADAM by:
84 |
85 | ```python
86 | from Adam.ADAM import ADAM
87 |
88 | with open("API_key.txt", 'r') as key_file:
89 | openai_api_key = key_file.read()
90 |
91 | ADAM = ADAM(
92 | mc_port=52832,
93 | llm_model_type='gpt-4-turbo',
94 | use_local_llm_service=False,
95 | openai_api_key=openai_api_key,
96 | parallel=True
97 | )
98 |
99 | ADAM.explore(['diamond'], [])
100 | ```
101 |
102 | # Resume from a checkpoint
103 |
104 | Our implementation has methods for automatic saving and automatic loading of checkpoints. If you want to continue from a
105 | checkpoint, you can use the following code.
106 |
107 | ```python
108 | from Adam.ADAM import ADAM
109 |
110 | with open("API_key.txt", 'r') as key_file:
111 | openai_api_key = key_file.read()
112 |
113 | ADAM = ADAM(
114 | mc_port=52832,
115 | llm_model_type='gpt-4-turbo',
116 | use_local_llm_service=False,
117 | openai_api_key=openai_api_key,
118 | auto_load_ckpt=True,
119 | parallel=True
120 | )
121 |
122 | ADAM.explore(['diamond'], [])
123 | ```
124 |
125 | If you want to continue from a specific checkpoint, you can use the following code.
126 | ```python
127 | from Adam.ADAM import ADAM
128 |
129 | with open("API_key.txt", 'r') as key_file:
130 | openai_api_key = key_file.read()
131 |
132 | ADAM = ADAM(
133 | mc_port=52832,
134 | llm_model_type='gpt-4-turbo',
135 | use_local_llm_service=False,
136 | openai_api_key=openai_api_key,
137 | load_ckpt_path='your_ckpt_path',
138 | parallel=True
139 | )
140 |
141 | ADAM.explore(['diamond'], [])
142 | ```
143 |
144 | # Paper and Citation
145 |
146 | ```bibtex
147 | @article{yu2024adam,
148 | title = {ADAM: An Embodied Causal Agent in Open-World Environments},
149 | author = {Shu Yu and Chaochao Lu},
150 | year = {2024},
151 | journal = {arXiv preprint arXiv:2410.22194}
152 | }
153 | ```
154 |
--------------------------------------------------------------------------------
/env/.gitignore:
--------------------------------------------------------------------------------
1 | # MCP-Reborn
2 | MCP-Reborn/
3 | run/
4 | *.jar
5 | config.json
6 |
7 | # Byte-compiled / optimized / DLL files
8 | __pycache__/
9 | *.py[cod]
10 | *$py.class
11 |
12 | # C extensions
13 | *.so
14 |
15 | # Distribution / packaging
16 | .Python
17 | build/
18 | develop-eggs/
19 | dist/
20 | downloads/
21 | eggs/
22 | .eggs/
23 | lib64/
24 | parts/
25 | sdist/
26 | var/
27 | wheels/
28 | share/python-wheels/
29 | *.egg-info/
30 | .installed.cfg
31 | *.egg
32 | MANIFEST
33 |
34 | # PyInstaller
35 | # Usually these files are written by a python script from a template
36 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
37 | *.manifest
38 | *.spec
39 |
40 | # Installer logs
41 | pip-log.txt
42 | pip-delete-this-directory.txt
43 |
44 | # Unit test / coverage reports
45 | htmlcov/
46 | .tox/
47 | .nox/
48 | .coverage
49 | .coverage.*
50 | .cache
51 | nosetests.xml
52 | coverage.xml
53 | *.cover
54 | *.py,cover
55 | .hypothesis/
56 | .pytest_cache/
57 | cover/
58 |
59 | # Translations
60 | *.mo
61 | *.pot
62 |
63 | # Django stuff:
64 | *.log
65 | local_settings.py
66 | db.sqlite3
67 | db.sqlite3-journal
68 |
69 | # Flask stuff:
70 | instance/
71 | .webassets-cache
72 |
73 | # Scrapy stuff:
74 | .scrapy
75 |
76 | # Sphinx documentation
77 | docs/_build/
78 |
79 | # PyBuilder
80 | .pybuilder/
81 | target/
82 |
83 | # Jupyter Notebook
84 | .ipynb_checkpoints
85 |
86 | # IPython
87 | profile_default/
88 | ipython_config.py
89 |
90 | # pyenv
91 | # For a library or package, you might want to ignore these files since the code is
92 | # intended to run in multiple environments; otherwise, check them in:
93 | # .python-version
94 |
95 | # pipenv
96 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
97 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
98 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
99 | # install all needed dependencies.
100 | #Pipfile.lock
101 |
102 | # poetry
103 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
104 | # This is especially recommended for binary packages to ensure reproducibility, and is more
105 | # commonly ignored for libraries.
106 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
107 | #poetry.lock
108 |
109 | # pdm
110 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
111 | #pdm.lock
112 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
113 | # in version control.
114 | # https://pdm.fming.dev/#use-with-ide
115 | .pdm.toml
116 |
117 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
118 | __pypackages__/
119 |
120 | # Celery stuff
121 | celerybeat-schedule
122 | celerybeat.pid
123 |
124 | # SageMath parsed files
125 | *.sage.py
126 |
127 | # Environments
128 | .env
129 | .venv
130 | env/
131 | venv/
132 | ENV/
133 | env.bak/
134 | venv.bak/
135 |
136 | # Spyder project settings
137 | .spyderproject
138 | .spyproject
139 |
140 | # Rope project settings
141 | .ropeproject
142 |
143 | # mkdocs documentation
144 | /site
145 |
146 | # mypy
147 | .mypy_cache/
148 | .dmypy.json
149 | dmypy.json
150 |
151 | # Pyre type checker
152 | .pyre/
153 |
154 | # pytype static type analyzer
155 | .pytype/
156 |
157 | # Cython debug symbols
158 | cython_debug/
159 |
160 | # PyCharm
161 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
162 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
163 | # and can be added to the global gitignore or merged into this file. For a more nuclear
164 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
165 | .idea/
166 |
167 | # Logs
168 | logs
169 | npm-debug.log*
170 | yarn-debug.log*
171 | yarn-error.log*
172 | lerna-debug.log*
173 | .pnpm-debug.log*
174 |
175 | # Diagnostic reports (https://nodejs.org/api/report.html)
176 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
177 |
178 | # Runtime data
179 | pids
180 | *.pid
181 | *.seed
182 | *.pid.lock
183 |
184 | # Directory for instrumented libs generated by jscoverage/JSCover
185 | lib-cov
186 |
187 | # Coverage directory used by tools like istanbul
188 | coverage
189 | *.lcov
190 |
191 | # nyc test coverage
192 | .nyc_output
193 |
194 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
195 | .grunt
196 |
197 | # Bower dependency directory (https://bower.io/)
198 | bower_components
199 |
200 | # node-waf configuration
201 | .lock-wscript
202 |
203 | # Compiled binary addons (https://nodejs.org/api/addons.html)
204 | build/Release
205 |
206 | # Dependency directories
207 | node_modules/
208 | jspm_packages/
209 |
210 | # Snowpack dependency directory (https://snowpack.dev/)
211 | web_modules/
212 |
213 | # TypeScript cache
214 | *.tsbuildinfo
215 |
216 | # Optional npm cache directory
217 | .npm
218 |
219 | # Optional eslint cache
220 | .eslintcache
221 |
222 | # Optional stylelint cache
223 | .stylelintcache
224 |
225 | # Microbundle cache
226 | .rpt2_cache/
227 | .rts2_cache_cjs/
228 | .rts2_cache_es/
229 | .rts2_cache_umd/
230 |
231 | # Optional REPL history
232 | .node_repl_history
233 |
234 | # Output of 'npm pack'
235 | *.tgz
236 |
237 | # Yarn Integrity file
238 | .yarn-integrity
239 |
240 | # dotenv environment variable files
241 | .env.development.local
242 | .env.test.local
243 | .env.production.local
244 | .env.local
245 |
246 | # parcel-bundler cache (https://parceljs.org/)
247 | .parcel-cache
248 |
249 | # Next.js build output
250 | .next
251 | out
252 |
253 | # Nuxt.js build / generate output
254 | .nuxt
255 | dist
256 |
257 | # Gatsby files
258 | .cache/
259 | # Comment in the public line in if your project uses Gatsby and not Next.js
260 | # https://nextjs.org/blog/next-9-1#public-directory-support
261 | # public
262 |
263 | # vuepress build output
264 | .vuepress/dist
265 |
266 | # vuepress v2.x temp and cache directory
267 | .temp
268 |
269 | # Docusaurus cache and generated files
270 | .docusaurus
271 |
272 | # Serverless directories
273 | .serverless/
274 |
275 | # FuseBox cache
276 | .fusebox/
277 |
278 | # DynamoDB Local files
279 | .dynamodb/
280 |
281 | # TernJS port file
282 | .tern-port
283 |
284 | # Stores VSCode versions used for testing VSCode extensions
285 | .vscode-test
286 |
287 | # yarn v2
288 | .yarn/cache
289 | .yarn/unplugged
290 | .yarn/build-state.yml
291 | .yarn/install-state.gz
292 | .pnp.*
293 |
294 | package-lock.json
--------------------------------------------------------------------------------
/env/__init__.py:
--------------------------------------------------------------------------------
1 | from .bridge import VoyagerEnv
2 |
--------------------------------------------------------------------------------
/env/bridge.py:
--------------------------------------------------------------------------------
1 | import os.path
2 | import time
3 | import warnings
4 | from typing import SupportsFloat, Any, Tuple, Dict
5 |
6 | import requests
7 | import json
8 |
9 | import gymnasium as gym
10 | from gymnasium.core import ObsType
11 |
12 | import utils as U
13 |
14 | from .minecraft_launcher import MinecraftInstance
15 | from .process_monitor import SubprocessMonitor
16 |
17 |
18 | class VoyagerEnv(gym.Env):
19 | def __init__(
20 | self,
21 | mc_port=None,
22 | azure_login=None,
23 | server_host="http://127.0.0.1",
24 | server_port=3000,
25 | request_timeout=600,
26 | log_path="./logs",
27 | visual_server_port=-1
28 | ):
29 | if not mc_port and not azure_login:
30 | raise ValueError("Either mc_port or azure_login must be specified")
31 | if mc_port and azure_login:
32 | warnings.warn(
33 | "Both mc_port and mc_login are specified, mc_port will be ignored"
34 | )
35 | self.mc_port = mc_port
36 | self.visual_server_port = visual_server_port
37 | self.azure_login = azure_login
38 | self.server = f"{server_host}:{server_port}"
39 | self.server_port = server_port
40 | self.request_timeout = request_timeout
41 | self.log_path = log_path
42 | self.mineflayer = self.get_mineflayer_process(server_port)
43 | if azure_login:
44 | self.mc_instance = self.get_mc_instance()
45 | else:
46 | self.mc_instance = None
47 | self.has_reset = False
48 | self.reset_options = None
49 | self.connected = False
50 | self.server_paused = False
51 |
52 | def get_mineflayer_process(self, server_port):
53 | U.f_mkdir(self.log_path, "mineflayer")
54 | file_path = os.path.abspath(os.path.dirname(__file__))
55 | return SubprocessMonitor(
56 | commands=[
57 | "node",
58 | U.f_join(file_path, "mineflayer/index.js"),
59 | str(server_port),
60 | str(self.visual_server_port)
61 | ],
62 | name="mineflayer",
63 | ready_match=r"Server started on port (\d+)",
64 | log_path=U.f_join(self.log_path, "mineflayer"),
65 | )
66 |
67 | def get_mc_instance(self):
68 | print("Creating Minecraft server")
69 | U.f_mkdir(self.log_path, "minecraft")
70 | return MinecraftInstance(
71 | **self.azure_login,
72 | mineflayer=self.mineflayer,
73 | log_path=U.f_join(self.log_path, "minecraft"),
74 | )
75 |
76 | def check_process(self):
77 | if self.mc_instance and not self.mc_instance.is_running:
78 | print("Starting Minecraft server")
79 | self.mc_instance.run()
80 | self.mc_port = self.mc_instance.port
81 | self.reset_options["port"] = self.mc_instance.port
82 | print(f"Server started on port {self.reset_options['port']}")
83 | retry = 0
84 | while not self.mineflayer.is_running:
85 | print("Mineflayer process has exited, restarting")
86 | self.mineflayer.run()
87 | if not self.mineflayer.is_running:
88 | if retry > 3:
89 | raise RuntimeError("Mineflayer process failed to start")
90 | else:
91 | continue
92 | print(self.mineflayer.ready_line)
93 | res = requests.post(
94 | f"{self.server}/start",
95 | json=self.reset_options,
96 | timeout=self.request_timeout,
97 | )
98 | if res.status_code != 200:
99 | self.mineflayer.stop()
100 | raise RuntimeError(
101 | f"Minecraft server reply with code {res.status_code}"
102 | )
103 | return res.json()
104 |
105 | def step(
106 | self,
107 | code: str,
108 | programs: str = "",
109 | ) -> Tuple[ObsType, SupportsFloat, bool, bool, Dict[str, Any]]:
110 | if not self.has_reset:
111 | raise RuntimeError("Environment has not been reset yet")
112 | self.check_process()
113 | self.unpause()
114 | data = {
115 | "code": code,
116 | "programs": programs,
117 | }
118 | res = requests.post(
119 | f"{self.server}/step", json=data, timeout=self.request_timeout
120 | )
121 | if res.status_code != 200:
122 | raise RuntimeError("Failed to step Minecraft server")
123 | returned_data = res.json()
124 | self.pause()
125 | return json.loads(returned_data)
126 |
127 | def render(self):
128 | raise NotImplementedError("render is not implemented")
129 |
130 | def reset(
131 | self,
132 | *,
133 | seed=None,
134 | options=None,
135 | ) -> Tuple[ObsType, Dict[str, Any]]:
136 | if options is None:
137 | options = {}
138 |
139 | if options.get("inventory", {}) and options.get("mode", "hard") != "hard":
140 | raise RuntimeError("inventory can only be set when options is hard")
141 |
142 | self.reset_options = {
143 | "port": self.mc_port,
144 | "reset": options.get("mode", "peaceful"),
145 | "inventory": options.get("inventory", {}),
146 | "equipment": options.get("equipment", []),
147 | "spread": options.get("spread", False),
148 | "waitTicks": options.get("wait_ticks", 5),
149 | "position": options.get("position", None),
150 | }
151 |
152 | self.unpause()
153 | self.mineflayer.stop()
154 | time.sleep(1) # wait for mineflayer to exit
155 |
156 | returned_data = self.check_process()
157 | self.has_reset = True
158 | self.connected = True
159 | # All the reset in step will be soft
160 | self.reset_options["reset"] = "soft"
161 | self.pause()
162 | return json.loads(returned_data)
163 |
164 | def close(self):
165 | self.unpause()
166 | if self.connected:
167 | res = requests.post(f"{self.server}/stop")
168 | if res.status_code == 200:
169 | self.connected = False
170 | if self.mc_instance:
171 | self.mc_instance.stop()
172 | self.mineflayer.stop()
173 | return not self.connected
174 |
175 | def pause(self):
176 | # if self.mineflayer.is_running and not self.server_paused:
177 | # res = requests.post(f"{self.server}/pause")
178 | # if res.status_code == 200:
179 | # self.server_paused = True
180 | return True # self.server_paused
181 |
182 | def unpause(self):
183 | # if self.mineflayer.is_running and self.server_paused:
184 | # res = requests.post(f"{self.server}/pause")
185 | # if res.status_code == 200:
186 | # self.server_paused = False
187 | # else:
188 | # print(res.json())
189 | return False # self.server_paused
190 |
--------------------------------------------------------------------------------
/env/minecraft_launcher.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 |
4 | import minecraft_launcher_lib
5 | import sys
6 | import utils as U
7 |
8 | from .process_monitor import SubprocessMonitor
9 |
10 |
11 | class MinecraftInstance:
12 | def __init__(
13 | self,
14 | client_id,
15 | redirect_url,
16 | secret_value,
17 | version,
18 | mineflayer,
19 | log_path="logs",
20 | ):
21 | self.client_id = client_id
22 | self.redirect_url = redirect_url
23 | self.secret_value = secret_value
24 | self.version = version
25 | self.log_path = log_path
26 | self.mc_dir = minecraft_launcher_lib.utils.get_minecraft_directory()
27 | self.port = None
28 |
29 | def stop_mineflayer():
30 | print("Stopping mineflayer")
31 | try:
32 | mineflayer.stop()
33 | except Exception as e:
34 | print(e)
35 |
36 | self.mc_command = self.get_mc_command()
37 | self.mc_process = SubprocessMonitor(
38 | commands=self.mc_command,
39 | name="minecraft",
40 | ready_match=r"Started serving on (\d+)",
41 | log_path=self.log_path,
42 | callback=stop_mineflayer,
43 | callback_match=r"\[Server thread/INFO\]: bot left the game",
44 | finished_callback=stop_mineflayer,
45 | )
46 |
47 | def get_mineflayer_process(self, server_port):
48 | U.f_mkdir(self.log_path, "../mineflayer")
49 | file_path = os.path.abspath(os.path.dirname(__file__))
50 | return SubprocessMonitor(
51 | commands=[
52 | "node",
53 | U.f_join(file_path, "mineflayer/index.js"),
54 | str(server_port),
55 | ],
56 | name="mineflayer",
57 | ready_match=r"Server started on port (\d+)",
58 | log_path=U.f_join(self.log_path, "mineflayer"),
59 | )
60 |
61 | def get_mc_command(self):
62 | file_path = os.path.abspath(os.path.dirname(__file__))
63 | if not U.f_exists(file_path, "config.json"):
64 | (
65 | login_url,
66 | state,
67 | code_verifier,
68 | ) = minecraft_launcher_lib.microsoft_account.get_secure_login_data(
69 | self.client_id, self.redirect_url
70 | )
71 | print(
72 | f"Please open {login_url} in your browser and copy the url you are redirected into the prompt below."
73 | )
74 | code_url = input()
75 |
76 | try:
77 | auth_code = (
78 | minecraft_launcher_lib.microsoft_account.parse_auth_code_url(
79 | code_url, state
80 | )
81 | )
82 | except AssertionError:
83 | print("States do not match!")
84 | sys.exit(1)
85 | except KeyError:
86 | print("Url not valid")
87 | sys.exit(1)
88 |
89 | login_data = minecraft_launcher_lib.microsoft_account.complete_login(
90 | self.client_id,
91 | self.secret_value,
92 | self.redirect_url,
93 | auth_code,
94 | code_verifier,
95 | )
96 |
97 | options = {
98 | "username": login_data["name"],
99 | "uuid": login_data["id"],
100 | "token": login_data["access_token"],
101 | }
102 | U.json_dump(options, file_path, "config.json")
103 | print(f"Login success, save to {U.f_join(file_path, 'config.json')}")
104 |
105 | options = U.json_load(file_path, "config.json")
106 | mc_command = minecraft_launcher_lib.command.get_minecraft_command(
107 | self.version, self.mc_dir, options
108 | )
109 |
110 | return mc_command
111 |
112 | def run(self):
113 | self.mc_process.run()
114 | pattern = r"Started serving on (\d+)"
115 | match = re.search(pattern, self.mc_process.ready_line)
116 | if match:
117 | self.port = int(match.group(1))
118 | print("The mc server is listening on port", self.port)
119 | else:
120 | raise RuntimeError("Port not found")
121 |
122 | def stop(self):
123 | self.mc_process.stop()
124 |
125 | @property
126 | def is_running(self):
127 | return self.mc_process.is_running
128 |
--------------------------------------------------------------------------------
/env/mineflayer/.prettierignore:
--------------------------------------------------------------------------------
1 | # Ignore artifacts:
2 | build
3 | coverage
--------------------------------------------------------------------------------
/env/mineflayer/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 4
3 | }
4 |
--------------------------------------------------------------------------------
/env/mineflayer/lib/observation/base.js:
--------------------------------------------------------------------------------
1 | class Observation {
2 | constructor(bot) {
3 | if (new.target === Observation) {
4 | throw new TypeError(
5 | "Cannot instantiate abstract class Observation"
6 | );
7 | }
8 |
9 | this.bot = bot;
10 | this.name = "Observation";
11 | }
12 |
13 | observe() {
14 | throw new TypeError("Method 'observe()' must be implemented.");
15 | }
16 |
17 | reset() {}
18 | }
19 |
20 | function inject(bot, obs_list) {
21 | bot.obsList = [];
22 | bot.cumulativeObs = [];
23 | bot.eventMemory = {};
24 | obs_list.forEach((obs) => {
25 | bot.obsList.push(new obs(bot));
26 | });
27 | bot.event = function (event_name) {
28 | let result = {};
29 | bot.obsList.forEach((obs) => {
30 | if (obs.name.startsWith("on") && obs.name !== event_name) {
31 | return;
32 | }
33 | result[obs.name] = obs.observe();
34 | });
35 | bot.cumulativeObs.push([event_name, result]);
36 | };
37 | bot.observe = function () {
38 | bot.event("observe");
39 | const result = bot.cumulativeObs;
40 | bot.cumulativeObs = [];
41 | return JSON.stringify(result);
42 | };
43 | }
44 |
45 | module.exports = { Observation, inject };
46 |
--------------------------------------------------------------------------------
/env/mineflayer/lib/observation/chests.js:
--------------------------------------------------------------------------------
1 | const { Observation } = require("./base");
2 |
3 | class Chests extends Observation {
4 | constructor(bot) {
5 | super(bot);
6 | this.name = "nearbyChests";
7 | this.chestsItems = {};
8 | bot.on("closeChest", (chestItems, position) => {
9 | this.chestsItems[position] = chestItems;
10 | });
11 | bot.on("removeChest", (chestPosition) => {
12 | this.chestsItems[chestPosition] = "Invalid";
13 | });
14 | }
15 |
16 | observe() {
17 | const chests = this.bot.findBlocks({
18 | matching: this.bot.registry.blocksByName.chest.id,
19 | maxDistance: 16,
20 | count: 999,
21 | });
22 | chests.forEach((chest) => {
23 | if (!this.chestsItems.hasOwnProperty(chest)) {
24 | this.chestsItems[chest] = "Unknown";
25 | }
26 | });
27 | return this.chestsItems;
28 | }
29 | }
30 |
31 | module.exports = Chests;
32 |
--------------------------------------------------------------------------------
/env/mineflayer/lib/observation/inventory.js:
--------------------------------------------------------------------------------
1 | const { Observation } = require("./base");
2 |
3 | class Inventory extends Observation {
4 | constructor(bot) {
5 | super(bot);
6 | this.name = "inventory";
7 | }
8 |
9 | observe() {
10 | return listItems(this.bot);
11 | }
12 | }
13 |
14 | function listItems(bot) {
15 | const items = getInventoryItems(bot);
16 | return items.reduce(itemToDict, {});
17 | }
18 |
19 | function getInventoryItems(bot) {
20 | const inventory = bot.currentWindow || bot.inventory;
21 | return inventory.items();
22 | }
23 |
24 | function itemToDict(acc, cur) {
25 | if (cur.name && cur.count) {
26 | //if both name and count property are defined
27 | if (acc[cur.name]) {
28 | //if the item is already in the dict
29 | acc[cur.name] += cur.count;
30 | } else {
31 | //if the item is not in the dict
32 | acc[cur.name] = cur.count;
33 | }
34 | }
35 | return acc;
36 | }
37 |
38 | //export modules
39 | module.exports = Inventory;
40 |
--------------------------------------------------------------------------------
/env/mineflayer/lib/observation/onChat.js:
--------------------------------------------------------------------------------
1 | const Observation = require("./base.js").Observation;
2 |
3 | class onChat extends Observation {
4 | constructor(bot) {
5 | super(bot);
6 | this.name = "onChat";
7 | this.obs = "";
8 | bot.on("chatEvent", (username, message) => {
9 | // Save entity status to local variable
10 | if (message.startsWith("/")) {
11 | return;
12 | }
13 |
14 | this.obs += message;
15 | this.bot.event(this.name);
16 | });
17 | }
18 |
19 | observe() {
20 | const result = this.obs;
21 | this.obs = "";
22 | return result;
23 | }
24 | }
25 |
26 | module.exports = onChat;
27 |
--------------------------------------------------------------------------------
/env/mineflayer/lib/observation/onError.js:
--------------------------------------------------------------------------------
1 | const Observation = require("./base.js").Observation;
2 |
3 | class onError extends Observation {
4 | constructor(bot) {
5 | super(bot);
6 | this.name = "onError";
7 | this.obs = null;
8 | bot.on("error", (err) => {
9 | // Save entity status to local variable
10 | this.obs = err;
11 | this.bot.event(this.name);
12 | });
13 | }
14 |
15 | observe() {
16 | const result = this.obs;
17 | this.obs = null;
18 | return result;
19 | }
20 | }
21 |
22 | module.exports = onError;
23 |
--------------------------------------------------------------------------------
/env/mineflayer/lib/observation/onSave.js:
--------------------------------------------------------------------------------
1 | const Observation = require("./base.js").Observation;
2 |
3 | class onSave extends Observation {
4 | constructor(bot) {
5 | super(bot);
6 | this.name = "onSave";
7 | this.obs = null;
8 | bot.on("save", (eventName) => {
9 | // Save entity status to local variable
10 | this.obs = eventName;
11 | this.bot.event(this.name);
12 | });
13 | }
14 |
15 | observe() {
16 | const result = this.obs;
17 | this.obs = null;
18 | return result;
19 | }
20 | }
21 |
22 | module.exports = onSave;
23 |
--------------------------------------------------------------------------------
/env/mineflayer/lib/observation/status.js:
--------------------------------------------------------------------------------
1 | const Observation = require("./base.js").Observation;
2 |
3 | class Status extends Observation {
4 | constructor(bot) {
5 | super(bot);
6 | this.name = "status";
7 | }
8 |
9 | observe() {
10 | return {
11 | health: this.bot.health,
12 | food: this.bot.food,
13 | saturation: this.bot.foodSaturation,
14 | oxygen: this.bot.oxygenLevel,
15 | position: this.bot.entity.position,
16 | velocity: this.bot.entity.velocity,
17 | yaw: this.bot.entity.yaw,
18 | pitch: this.bot.entity.pitch,
19 | onGround: this.bot.entity.onGround,
20 | equipment: this.getEquipment(),
21 | name: this.bot.entity.username,
22 | timeSinceOnGround: this.bot.entity.timeSinceOnGround,
23 | isInWater: this.bot.entity.isInWater,
24 | isInLava: this.bot.entity.isInLava,
25 | isInWeb: this.bot.entity.isInWeb,
26 | isCollidedHorizontally: this.bot.entity.isCollidedHorizontally,
27 | isCollidedVertically: this.bot.entity.isCollidedVertically,
28 | biome: this.bot.blockAt(this.bot.entity.position)
29 | ? this.bot.blockAt(this.bot.entity.position).biome.name
30 | : "None",
31 | entities: this.getEntities(),
32 | timeOfDay: this.getTime(),
33 | inventoryUsed: this.bot.inventoryUsed(),
34 | elapsedTime: this.bot.globalTickCounter,
35 | };
36 | }
37 |
38 | itemToObs(item) {
39 | if (!item) return null;
40 | return item.name;
41 | }
42 |
43 | getTime() {
44 | const timeOfDay = this.bot.time.timeOfDay;
45 | let time = "";
46 | if (timeOfDay < 1000) {
47 | time = "sunrise";
48 | } else if (timeOfDay < 6000) {
49 | time = "day";
50 | } else if (timeOfDay < 12000) {
51 | time = "noon";
52 | } else if (timeOfDay < 13000) {
53 | time = "sunset";
54 | } else if (timeOfDay < 18000) {
55 | time = "night";
56 | } else if (timeOfDay < 22000) {
57 | time = "midnight";
58 | } else {
59 | time = "sunrise";
60 | }
61 | return time;
62 | }
63 |
64 | // For each item in equipment, if it exists, return the name of the item
65 | // otherwise return null
66 | getEquipment() {
67 | const slots = this.bot.inventory.slots;
68 | const mainHand = this.bot.heldItem;
69 | return slots
70 | .slice(5, 9)
71 | .concat(mainHand, slots[45])
72 | .map(this.itemToObs);
73 | }
74 |
75 | getEntities() {
76 | const entities = this.bot.entities;
77 | if (!entities) return {};
78 | // keep all monsters in one list, keep other mobs in another list
79 | const mobs = {};
80 | for (const id in entities) {
81 | const entity = entities[id];
82 | if (!entity.displayName) continue;
83 | if (entity.name === "player" || entity.name === "item") continue;
84 | if (entity.position.distanceTo(this.bot.entity.position) < 32) {
85 | if (!mobs[entity.name]) {
86 | mobs[entity.name] = entity.position.distanceTo(
87 | this.bot.entity.position
88 | );
89 | } else if (
90 | mobs[entity.name] >
91 | entity.position.distanceTo(this.bot.entity.position)
92 | ) {
93 | mobs[entity.name] = entity.position.distanceTo(
94 | this.bot.entity.position
95 | );
96 | }
97 | }
98 | }
99 | return mobs;
100 | }
101 | }
102 |
103 | module.exports = Status;
104 |
--------------------------------------------------------------------------------
/env/mineflayer/lib/observation/voxels.js:
--------------------------------------------------------------------------------
1 | // Blocks = require("./blocks")
2 | const { Observation } = require("./base");
3 |
4 | class Voxels extends Observation {
5 | constructor(bot) {
6 | super(bot);
7 | this.name = "voxels";
8 | }
9 |
10 | observe() {
11 | return Array.from(getSurroundingBlocks(this.bot, 8, 2, 8));
12 | }
13 | }
14 |
15 | class BlockRecords extends Observation {
16 | constructor(bot) {
17 | super(bot);
18 | this.name = "blockRecords";
19 | this.records = new Set();
20 | this.tick = 0;
21 | bot.on("physicsTick", () => {
22 | this.tick++;
23 | if (this.tick >= 100) {
24 | const items = getInventoryItems(this.bot);
25 | getSurroundingBlocks(this.bot, 8, 2, 8).forEach((block) => {
26 | if (!items.has(block)) this.records.add(block);
27 | });
28 | this.tick = 0;
29 | }
30 | });
31 | }
32 |
33 | observe() {
34 | return Array.from(this.records);
35 | }
36 |
37 | reset() {
38 | this.records = new Set();
39 | }
40 | }
41 |
42 | function getSurroundingBlocks(bot, x_distance, y_distance, z_distance) {
43 | const surroundingBlocks = new Set();
44 |
45 | for (let x = -x_distance; x <= x_distance; x++) {
46 | for (let y = -y_distance; y <= y_distance; y++) {
47 | for (let z = -z_distance; z <= z_distance; z++) {
48 | const block = bot.blockAt(bot.entity.position.offset(x, y, z));
49 | if (block && block.type !== 0) {
50 | surroundingBlocks.add(block.name);
51 | }
52 | }
53 | }
54 | }
55 | // console.log(surroundingBlocks);
56 | return surroundingBlocks;
57 | }
58 |
59 | function getInventoryItems(bot) {
60 | const items = new Set();
61 | bot.inventory.items().forEach((item) => {
62 | if (item) items.add(item.name);
63 | });
64 | return items;
65 | }
66 |
67 | module.exports = { Voxels, BlockRecords };
68 |
--------------------------------------------------------------------------------
/env/mineflayer/lib/skillLoader.js:
--------------------------------------------------------------------------------
1 | function inject(bot) {
2 | bot._sleep = bot.sleep;
3 | bot.sleep = async (bedBlock) => {
4 | await bot.waitForTicks(20);
5 | await bot._sleep(bedBlock);
6 | await bot.waitForTicks(135);
7 | };
8 |
9 | bot._fish = bot.fish;
10 | bot.fish = async () => {
11 | if (bot.heldItem?.name !== "fishing_rod") {
12 | bot.chat("I'm not holding a fishing rod!");
13 | return;
14 | }
15 | let timeout = null;
16 | await Promise.race([
17 | bot._fish(),
18 | new Promise(
19 | (resolve, reject) =>
20 | (timeout = setTimeout(() => {
21 | bot.activateItem();
22 | reject(
23 | new Error(
24 | "Finishing timeout, make sure you get to and look at a water block!"
25 | )
26 | );
27 | }, 60000))
28 | ),
29 | ]);
30 | clearTimeout(timeout);
31 | await bot.waitForTicks(20);
32 | };
33 |
34 | bot._consume = bot.consume;
35 | bot.consume = async () => {
36 | // action_count.activateItem++;
37 | await bot._consume();
38 | await bot.waitForTicks(20);
39 | };
40 |
41 | bot._useOn = bot.useOn;
42 | bot.useOn = async (entity) => {
43 | if (entity.position.distanceTo(bot.entity.position) > 6) {
44 | bot.chat("Please goto a place near the entity first!");
45 | return;
46 | }
47 | await bot._useOn(entity);
48 | await bot.waitForTicks(20);
49 | };
50 |
51 | bot._activateBlock = bot.activateBlock;
52 | bot.activateBlock = async (block) => {
53 | if (block.position.distanceTo(bot.entity.position) > 6) {
54 | bot.chat("Please goto a place near the block first!");
55 | return;
56 | }
57 | // action_count.activateBlock++;
58 | await bot._activateBlock(block);
59 | };
60 |
61 | bot._chat = bot.chat;
62 | bot.chat = (message) => {
63 | // action_count.chat++;
64 | bot.emit("chatEvent", "bot", message);
65 | bot._chat(message);
66 | };
67 |
68 | bot.inventoryUsed = () => {
69 | return bot.inventory.slots.slice(9, 45).filter((item) => item !== null)
70 | .length;
71 | };
72 |
73 | bot.save = function (eventName) {
74 | bot.emit("save", eventName);
75 | };
76 | }
77 |
78 | // export all control_primitives
79 | module.exports = { inject };
80 |
--------------------------------------------------------------------------------
/env/mineflayer/lib/utils.js:
--------------------------------------------------------------------------------
1 | let gameTimeCounter = 0;
2 | let gameTimeList = [];
3 | const initCounter = (bot) => {
4 | gameTimeList = [];
5 | for (let i = 0; i < 13000; i += 1000) {
6 | gameTimeList.push(i);
7 | }
8 | for (let i = 13000; i < 24000; i += 2000) {
9 | gameTimeList.push(i);
10 | }
11 | const timeOfDay = bot.time.timeOfDay;
12 | for (let i = 0; i < gameTimeList.length; i++) {
13 | if (gameTimeList[i] > timeOfDay) {
14 | gameTimeCounter = i - 1;
15 | break;
16 | }
17 | }
18 | };
19 |
20 | const getNextTime = () => {
21 | gameTimeCounter++;
22 | if (gameTimeCounter >= gameTimeList.length) {
23 | gameTimeCounter = 0;
24 | }
25 | return gameTimeList[gameTimeCounter];
26 | };
27 |
28 | module.exports = {
29 | initCounter,
30 | getNextTime,
31 | };
32 |
--------------------------------------------------------------------------------
/env/mineflayer/mineflayer-collectblock/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # Next.js build output
79 | .next
80 |
81 | # Nuxt.js build / generate output
82 | .nuxt
83 | dist
84 |
85 | # Gatsby files
86 | .cache/
87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
88 | # https://nextjs.org/blog/next-9-1#public-directory-support
89 | # public
90 |
91 | # vuepress build output
92 | .vuepress/dist
93 |
94 | # Serverless directories
95 | .serverless/
96 |
97 | # FuseBox cache
98 | .fusebox/
99 |
100 | # DynamoDB Local files
101 | .dynamodb/
102 |
103 | # TernJS port file
104 | .tern-port
105 |
106 | lib/
107 | package-lock.json
108 |
--------------------------------------------------------------------------------
/env/mineflayer/mineflayer-collectblock/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 TheDudeFromCI
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/env/mineflayer/mineflayer-collectblock/README.md:
--------------------------------------------------------------------------------
1 | mineflayer-collectblock
2 | A small utility plugin for allowing users to collect blocks using a higher level API.
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | ---
14 | ## This is a modified version to better support Voyager
15 |
16 | ## Showcase
17 |
18 | You can see a video of the plugin in action, [here.](https://youtu.be/5T_rcCnNnf4)
19 | The source code of the bot in the video can be seen in the examples folder, [here.](https://github.com/TheDudeFromCI/mineflayer-collectblock/blob/master/examples/collector.js)
20 |
21 | ### Description
22 |
23 | This plugin is a wrapper for mineflayer that allows for easier API usage when collecting blocks or item drops. This plugin is designed to reduce some of the boilerplate code based around the act of pathfinding to a block _(handled by_ ***mineflayer-pathfinder***_)_, selecting the best tool to mine that block _(handled by_ ***mineflayer-tool***_)_, actually mining it, then moving to collect the item drops from that block. This plugin allows for all of that basic concept to be wrapped up into a single API function.
24 |
25 | In addition to the usage above, some additional quality of life features are available in this plugin. These include the ability to automatically deposit items into a chest when the bot's inventory is full, collecting new tools from a chest if the bot doesn't currently have a required tool _(also handled by_ ***mineflayer-tool***_)_, and allowing for queueing of multiple blocks or item drops to the collection task, so they can be processed later.
26 |
27 | ### Getting Started
28 |
29 | This plugin is built using Node and can be installed using:
30 | ```bash
31 | npm install --save mineflayer-collectblock
32 | ```
33 |
34 | ### Simple Bot
35 |
36 | The brief description goes here.
37 |
38 | ```js
39 | // Create your bot
40 | const mineflayer = require("mineflayer")
41 | const bot = mineflayer.createBot({
42 | host: 'localhost',
43 | username: 'Player',
44 | })
45 | let mcData
46 |
47 | // Load collect block
48 | bot.loadPlugin(require('mineflayer-collectblock').plugin)
49 |
50 | async function collectGrass() {
51 | // Find a nearby grass block
52 | const grass = bot.findBlock({
53 | matching: mcData.blocksByName.grass_block.id,
54 | maxDistance: 64
55 | })
56 |
57 | if (grass) {
58 | // If we found one, collect it.
59 | try {
60 | await bot.collectBlock.collect(grass)
61 | collectGrass() // Collect another grass block
62 | } catch (err) {
63 | console.log(err) // Handle errors, if any
64 | }
65 | }
66 | }
67 |
68 | // On spawn, start collecting all nearby grass
69 | bot.once('spawn', () => {
70 | mcData = require('minecraft-data')(bot.version)
71 | collectGrass()
72 | })
73 | ```
74 |
75 | ### Documentation
76 |
77 | [API](https://github.com/TheDudeFromCI/mineflayer-collectblock/blob/master/docs/api.md)
78 |
79 | [Examples](https://github.com/TheDudeFromCI/mineflayer-collectblock/tree/master/examples)
80 |
81 | ### License
82 |
83 | This project uses the [MIT](https://github.com/TheDudeFromCI/mineflayer-collectblock/blob/master/LICENSE) license.
84 |
85 | ### Contributions
86 |
87 | This project is accepting PRs and Issues. See something you think can be improved? Go for it! Any and all help is highly appreciated!
88 |
89 | For larger changes, it is recommended to discuss these changes in the issues tab before writing any code. It's also preferred to make many smaller PRs than one large one, where applicable.
90 |
--------------------------------------------------------------------------------
/env/mineflayer/mineflayer-collectblock/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-cayman
--------------------------------------------------------------------------------
/env/mineflayer/mineflayer-collectblock/docs/api.md:
--------------------------------------------------------------------------------
1 | # API
2 |
3 | Welcome to the *mineflayer-collectblock* API documentation page.
4 |
5 | ## Table of Contents
6 |
7 | - [1. Summary](#1-summary)
8 | - [Properties](#properties)
9 | - [`bot.collectblock.movements: Movements`](#botcollectblockmovements-movements)
10 | - [Functions](#functions)
11 | - [collect](#collect)
12 | - [Options:](#options)
13 |
14 | ## 1. Summary
15 |
16 | The collect block plugin is a utility plugin that can be used to help make collecting blocks and item drops very easy, using only a single API call. No need to worry about pathfinding to the block, selecting the right tool, or moving to pick up the item drop after mining.
17 |
18 | ## Properties
19 |
20 | ### `bot.collectblock.movements: Movements`
21 |
22 | The movements object used by the pathfinder plugin to define the movement configuration. This object is passed to the pathfinder plugin when any API from this plugin is called in order to control how pathfinding should work when collecting the given blocks or item.
23 |
24 | If set to null, the pathfinder plugin movements is not updated.
25 |
26 | Defaults to a new movements object instance.
27 |
28 | ## Functions
29 |
30 | ### collect
31 |
32 | Usage: `bot.collectblock.collect(target: Collectable | Collectable[], options?: CollectOptions, cb: (err?: Error) => void): void`
33 |
34 | Causes the bot to collect the given block, item drop, or list of those. If the target is a block, the bot will move to the block, mine it, and pick up the item drop. If the target is an item drop, the bot will move to the item drop and pick it up. If the target is a list of collectables, the bot will move from target to target in order of closest to furthest and collect each target in turn.
35 |
36 | #### Options:
37 |
38 | * `append: boolean`
39 |
40 | If true, the target(s) will be appended to the existing target list instead of starting a new task. Defaults to false.
41 |
42 | * `ignoreNoPath: boolean`
43 |
44 | If true, errors will not be thrown when a path to the target block cannot be found. The bot will attempt to choose the best available position it can find, instead. Errors are still thrown if the bot cannot interact with the block from it's final location. Defaults to false.
45 |
46 | * `chestLocations: Vec3[]`
47 |
48 | Gets the list of chest locations to use when storing items after the bot's inventory becomes full. If undefined, it defaults to the chest location list on the bot.collectBlock plugin.
49 |
50 | * `itemFilter: ItemFilter`
51 |
52 | When transferring items to a chest, this filter is used to determine what items are allowed to be moved, and what items aren't allowed to be moved. Defaults to the item filter specified on the bot.collectBlock plugin.
--------------------------------------------------------------------------------
/env/mineflayer/mineflayer-collectblock/examples/collector.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This bot example show how to direct a bot to collect a specific block type
3 | * or a group of nearby blocks of that type.
4 | */
5 |
6 | const mineflayer = require('mineflayer')
7 | const collectBlock = require('mineflayer-collectblock').plugin
8 |
9 | if (process.argv.length < 4 || process.argv.length > 6) {
10 | console.log('Usage : node collector.js [] []')
11 | process.exit(1)
12 | }
13 |
14 | const bot = mineflayer.createBot({
15 | host: process.argv[2],
16 | port: process.argv[3],
17 | username: process.argv[4] || 'collector',
18 | password: process.argv[5]
19 | })
20 |
21 | bot.loadPlugin(collectBlock)
22 |
23 | let mcData
24 | bot.once('spawn', () => {
25 | mcData = require('minecraft-data')(bot.version)
26 | })
27 |
28 | bot.on('chat', async (username, message) => {
29 | const args = message.split(' ')
30 | if (args[0] !== 'collect') return
31 |
32 | let count = 1
33 | if (args.length === 3) count = parseInt(args[1])
34 |
35 | let type = args[1]
36 | if (args.length === 3) type = args[2]
37 |
38 | const blockType = mcData.blocksByName[type]
39 | if (!blockType) {
40 | return
41 | }
42 |
43 | const blocks = bot.findBlocks({
44 | matching: blockType.id,
45 | maxDistance: 64,
46 | count: count
47 | })
48 |
49 | if (blocks.length === 0) {
50 | bot.chat("I don't see that block nearby.")
51 | return
52 | }
53 |
54 | const targets = []
55 | for (let i = 0; i < Math.min(blocks.length, count); i++) {
56 | targets.push(bot.blockAt(blocks[i]))
57 | }
58 |
59 | bot.chat(`Found ${targets.length} ${type}(s)`)
60 |
61 | try {
62 | await bot.collectBlock.collect(targets)
63 | // All blocks have been collected.
64 | bot.chat('Done')
65 | } catch (err) {
66 | // An error occurred, report it.
67 | bot.chat(err.message)
68 | console.log(err)
69 | }
70 | })
71 |
--------------------------------------------------------------------------------
/env/mineflayer/mineflayer-collectblock/examples/oreMiner.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This bot example shows how to collect a vein of ores quickly after only finding a single block.
3 | * This makes it easy to collect a vein of ores or mine a tree without looking for every block in the
4 | * area.
5 | */
6 |
7 | const mineflayer = require('mineflayer')
8 | const collectBlock = require('mineflayer-collectblock').plugin
9 |
10 | if (process.argv.length < 4 || process.argv.length > 6) {
11 | console.log('Usage : node oreMiner.js [] []')
12 | process.exit(1)
13 | }
14 |
15 | const bot = mineflayer.createBot({
16 | host: process.argv[2],
17 | port: process.argv[3],
18 | username: process.argv[4] || 'oreMiner',
19 | password: process.argv[5]
20 | })
21 |
22 | bot.loadPlugin(collectBlock)
23 |
24 | let mcData
25 | bot.once('spawn', () => {
26 | mcData = require('minecraft-data')(bot.version)
27 | })
28 |
29 | bot.on('chat', async (username, message) => {
30 | const args = message.split(' ')
31 | if (args[0] !== 'collect') return
32 |
33 | const blockType = mcData.blocksByName[args[1]]
34 | if (!blockType) {
35 | bot.chat(`I don't know any blocks named ${args[1]}.`)
36 | return
37 | }
38 |
39 | const block = bot.findBlock({
40 | matching: blockType.id,
41 | maxDistance: 64
42 | })
43 |
44 | if (!block) {
45 | bot.chat("I don't see that block nearby.")
46 | return
47 | }
48 |
49 | const targets = bot.collectBlock.findFromVein(block)
50 | try {
51 | await bot.collectBlock.collect(targets)
52 | // All blocks have been collected.
53 | bot.chat('Done')
54 | } catch (err) {
55 | // An error occurred, report it.
56 | bot.chat(err.message)
57 | console.log(err)
58 | }
59 | })
60 |
--------------------------------------------------------------------------------
/env/mineflayer/mineflayer-collectblock/examples/storageBot.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This bot example shows how to use the chest filling mechanic of the plugin.
3 | * Simply provide a given storage chest, and the bot will automatically try and
4 | * store it's inventory in that chest when the bot's inventory becomes full.
5 | */
6 |
7 | if (process.argv.length < 4 || process.argv.length > 6) {
8 | console.log('Usage : node storageBot.js [] []')
9 | process.exit(1)
10 | }
11 |
12 | // Load your libraries
13 | const mineflayer = require('mineflayer')
14 | const collectBlock = require('mineflayer-collectblock').plugin
15 |
16 | // Create your bot
17 | const bot = mineflayer.createBot({
18 | host: process.argv[2],
19 | port: parseInt(process.argv[3]),
20 | username: process.argv[4] ? process.argv[4] : 'storageBot',
21 | password: process.argv[5]
22 | })
23 |
24 | // Load the collect block plugin
25 | bot.loadPlugin(collectBlock)
26 |
27 | // Load mcData on login
28 | let mcData
29 | bot.once('login', () => {
30 | mcData = require('minecraft-data')(bot.version)
31 | })
32 |
33 | // On spawn, try to find any nearby chests and save those as storage locations.
34 | // When the bot's inventory becomes too full, it will empty it's inventory into
35 | // these chests before collecting more resources. If a chest gets full, it moves
36 | // to the next one in order until it's inventory is empty or it runs out of chests.
37 | bot.once('spawn', () => {
38 | bot.collectBlock.chestLocations = bot.findBlocks({
39 | matching: mcData.blocksByName.chest.id,
40 | maxDistance: 16,
41 | count: 999999 // Get as many chests as we can
42 | })
43 |
44 | if (bot.collectBlock.chestLocations.length === 0) {
45 | bot.chat("I don't see any chests nearby.")
46 | } else {
47 | for (const chestPos of bot.collectBlock.chestLocations) {
48 | bot.chat(`I found a chest at ${chestPos}`)
49 | }
50 | }
51 | })
52 |
53 | // Wait for someone to say something
54 | bot.on('chat', async (username, message) => {
55 | // If the player says something start starts with "collect"
56 | // Otherwise, do nothing
57 | const args = message.split(' ')
58 | if (args[0] !== 'collect') return
59 |
60 | // If the player specifies a number, collect that many. Otherwise, default to 1.
61 | let count = 1
62 | if (args.length === 3) count = parseInt(args[1])
63 |
64 | // If a number was given the item number is the 3rd arg, not the 2nd.
65 | let type = args[1]
66 | if (args.length === 3) type = args[2]
67 |
68 | // Get the id of that block type for this version of Minecraft.
69 | const blockType = mcData.blocksByName[type]
70 | if (!blockType) {
71 | bot.chat(`I don't know any blocks named ${type}.`)
72 | return
73 | }
74 |
75 | // Find all nearby blocks of that type, up to the given count, within 64 blocks.
76 | const blocks = bot.findBlocks({
77 | matching: blockType.id,
78 | maxDistance: 64,
79 | count: count
80 | })
81 |
82 | // Complain if we can't find any nearby blocks of that type.
83 | if (blocks.length === 0) {
84 | bot.chat("I don't see that block nearby.")
85 | return
86 | }
87 |
88 | // Convert the block position array into a block array to pass to collect block.
89 | const targets = []
90 | for (let i = 0; i < Math.min(blocks.length, count); i++) {
91 | targets.push(bot.blockAt(blocks[i]))
92 | }
93 |
94 | // Announce what we found.
95 | bot.chat(`Found ${targets.length} ${type}(s)`)
96 |
97 | // Tell the bot to collect all of the given blocks in the block list.
98 | try {
99 | await bot.collectBlock.collect(targets)
100 | // All blocks have been collected.
101 | bot.chat('Done')
102 | } catch (err) {
103 | // An error occurred, report it.
104 | bot.chat(err.message)
105 | console.log(err)
106 | }
107 | })
108 |
--------------------------------------------------------------------------------
/env/mineflayer/mineflayer-collectblock/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mineflayer-collectblock",
3 | "version": "1.4.1",
4 | "description": "A simple utility plugin for Mineflayer that add a higher level API for collecting blocks.",
5 | "main": "lib/index.js",
6 | "types": "lib/index.d.ts",
7 | "scripts": {
8 | "build": "ts-standard && tsc && require-self",
9 | "clean": "rm -rf lib",
10 | "test": "test"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/TheDudeFromCI/mineflayer-collectblock.git"
15 | },
16 | "keywords": [
17 | "mineflayer",
18 | "plugin",
19 | "api",
20 | "utility",
21 | "helper",
22 | "collect"
23 | ],
24 | "author": "TheDudeFromCI",
25 | "license": "MIT",
26 | "bugs": {
27 | "url": "https://github.com/TheDudeFromCI/mineflayer-collectblock/issues"
28 | },
29 | "homepage": "https://github.com/TheDudeFromCI/mineflayer-collectblock#readme",
30 | "dependencies": {
31 | "mineflayer": "^4.0.0",
32 | "mineflayer-pathfinder": "^2.1.1",
33 | "mineflayer-tool": "^1.1.0"
34 | },
35 | "devDependencies": {
36 | "@types/node": "^18.6.4",
37 | "require-self": "^0.2.3",
38 | "ts-standard": "^11.0.0",
39 | "typescript": "^4.1.3"
40 | },
41 | "files": [
42 | "lib/**/*"
43 | ]
44 | }
45 |
--------------------------------------------------------------------------------
/env/mineflayer/mineflayer-collectblock/src/BlockVeins.ts:
--------------------------------------------------------------------------------
1 | import { Bot } from 'mineflayer'
2 | import { Block } from 'prismarine-block'
3 |
4 | export function findFromVein (bot: Bot, block: Block, maxBlocks: number, maxDistance: number, floodRadius: number): Block[] {
5 | const targets: Block[] = []
6 | const open: Block[] = [block]
7 | const type = block.type
8 | const center = block.position
9 |
10 | for (let i = 0; i < maxBlocks; i++) {
11 | const next = open.pop()
12 | if (next == null) break
13 |
14 | targets.push(next)
15 |
16 | for (let x = -floodRadius; x <= floodRadius; x++) {
17 | for (let y = -floodRadius; y <= floodRadius; y++) {
18 | for (let z = -floodRadius; z <= floodRadius; z++) {
19 | const neighborPos = next.position.offset(x, y, z)
20 | if (neighborPos.manhattanDistanceTo(center) > maxDistance) continue
21 |
22 | const neighbor = bot.blockAt(neighborPos)
23 | if (neighbor == null || neighbor.type !== type) continue
24 |
25 | if (targets.includes(neighbor)) continue
26 | if (open.includes(neighbor)) continue
27 |
28 | open.push(neighbor)
29 | }
30 | }
31 | }
32 | }
33 |
34 | return targets
35 | }
36 |
--------------------------------------------------------------------------------
/env/mineflayer/mineflayer-collectblock/src/Inventory.ts:
--------------------------------------------------------------------------------
1 | import { Bot } from 'mineflayer'
2 | import { Callback } from './CollectBlock'
3 | import { Vec3 } from 'vec3'
4 | import { error } from './Util'
5 | import { Item } from 'prismarine-item'
6 | import { goals } from 'mineflayer-pathfinder'
7 | import { callbackify } from 'util'
8 |
9 | export type ItemFilter = (item: Item) => boolean
10 |
11 | function getClosestChest (bot: Bot, chestLocations: Vec3[]): Vec3 | null {
12 | let chest = null
13 | let distance = 0
14 |
15 | for (const c of chestLocations) {
16 | const dist = c.distanceTo(bot.entity.position)
17 | if (chest == null || dist < distance) {
18 | chest = c
19 | distance = dist
20 | }
21 | }
22 |
23 | if (chest != null) {
24 | chestLocations.splice(chestLocations.indexOf(chest), 1)
25 | }
26 |
27 | return chest
28 | }
29 |
30 | export async function emptyInventoryIfFull (bot: Bot, chestLocations: Vec3[], itemFilter: ItemFilter, cb?: Callback): Promise {
31 | // @ts-expect-error
32 | if (cb != null) return callbackify(emptyInventoryIfFull)(bot, chestLocations, cb)
33 | if (bot.inventory.emptySlotCount() > 0) return
34 | return await emptyInventory(bot, chestLocations, itemFilter)
35 | }
36 |
37 | export async function emptyInventory (bot: Bot, chestLocations: Vec3[], itemFilter: ItemFilter, cb?: Callback): Promise {
38 | // @ts-expect-error
39 | if (cb != null) return callbackify(emptyInventory)(bot, chestLocations, cb)
40 | if (chestLocations.length === 0) {
41 | throw error('NoChests', 'There are no defined chest locations!')
42 | }
43 |
44 | // Shallow clone so we can safely remove chests from the list that are full.
45 | chestLocations = [...chestLocations]
46 |
47 | while (true) {
48 | const chest = getClosestChest(bot, chestLocations)
49 | if (chest == null) {
50 | throw error('NoChests', 'All chests are full.')
51 | }
52 | const hasRemaining = await tryEmptyInventory(bot, chest, itemFilter)
53 | if (!hasRemaining) return
54 | }
55 | }
56 |
57 | async function tryEmptyInventory (bot: Bot, chestLocation: Vec3, itemFilter: ItemFilter, cb?: (err: Error | undefined, hasRemaining: boolean) => void): Promise {
58 | // @ts-expect-error
59 | if (cb != null) return callbackify(tryEmptyInventory)(bot, chestLocation, itemFilter, cb)
60 | await gotoChest(bot, chestLocation)
61 | return await placeItems(bot, chestLocation, itemFilter)
62 | }
63 |
64 | async function gotoChest (bot: Bot, location: Vec3, cb?: Callback): Promise {
65 | // @ts-expect-error
66 | if (cb != null) return callbackify(gotoChest)(bot, location)
67 | await bot.pathfinder.goto(new goals.GoalGetToBlock(location.x, location.y, location.z))
68 | }
69 |
70 | async function placeItems (bot: Bot, chestPos: Vec3, itemFilter: ItemFilter, cb?: (err: Error | undefined, hasRemaining: boolean) => void): Promise {
71 | // @ts-expect-error
72 | if (cb != null) return callbackify(placeItems)(bot, chestPos, itemFilter, cb)
73 | const chestBlock = bot.blockAt(chestPos)
74 | if (chestBlock == null) {
75 | throw error('UnloadedChunk', 'Chest is in an unloaded chunk!')
76 | }
77 | const chest = await bot.openChest(chestBlock)
78 | for (const item of bot.inventory.items()) {
79 | if (!itemFilter(item)) continue
80 | if (chest.firstEmptyContainerSlot() === null) {
81 | // We have items that didn't fit.
82 | return true
83 | }
84 | await chest.deposit(item.type, item.metadata, item.count)
85 | }
86 | return false
87 | }
88 |
--------------------------------------------------------------------------------
/env/mineflayer/mineflayer-collectblock/src/Targets.ts:
--------------------------------------------------------------------------------
1 | import { Bot } from 'mineflayer'
2 | import { Block } from 'prismarine-block'
3 | import { Entity } from 'prismarine-entity'
4 |
5 | export type Collectable = Block | Entity
6 |
7 | export class Targets {
8 | private readonly bot: Bot
9 | private targets: Collectable[] = []
10 |
11 | constructor (bot: Bot) {
12 | this.bot = bot
13 | }
14 |
15 | appendTargets (targets: Collectable[]): void {
16 | for (const target of targets) {
17 | this.appendTarget(target)
18 | }
19 | }
20 |
21 | appendTarget (target: Collectable): void {
22 | if (this.targets.includes(target)) return
23 | this.targets.push(target)
24 | }
25 |
26 | /**
27 | * Gets the closest target to the bot in this list.
28 | *
29 | * @returns The closest target, or null if there are no targets.
30 | */
31 | getClosest (): Collectable | null {
32 | let closest: Collectable | null = null
33 | let distance: number = 0
34 |
35 | for (const target of this.targets) {
36 | const dist = target.position.distanceTo(this.bot.entity.position)
37 |
38 | if (closest == null || dist < distance) {
39 | closest = target
40 | distance = dist
41 | }
42 | }
43 |
44 | return closest
45 | }
46 |
47 | get empty (): boolean {
48 | return this.targets.length === 0
49 | }
50 |
51 | clear (): void {
52 | this.targets.length = 0
53 | }
54 |
55 | removeTarget (target: Collectable): void {
56 | const index = this.targets.indexOf(target)
57 | if (index < 0) return
58 | this.targets.splice(index, 1)
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/env/mineflayer/mineflayer-collectblock/src/TaskQueue.ts:
--------------------------------------------------------------------------------
1 | import type { Callback } from './index'
2 | export type Task = (cb: Callback) => void
3 | export type SyncTask = () => void
4 |
5 | /**
6 | * A simple utility class for queuing up a series of async tasks to execute.
7 | */
8 | export class TaskQueue {
9 | private tasks: Task[] = []
10 |
11 | /**
12 | * If true, the task list will stop executing if one of the tasks throws an error.
13 | */
14 | readonly stopOnError: boolean = true
15 |
16 | /**
17 | * Adds a new async task to this queue. The provided callback should be executed when
18 | * the async task is complete.
19 | *
20 | * @param task - The async task to add.
21 | */
22 | add (task: Task): void {
23 | this.tasks.push(task)
24 | }
25 |
26 | /**
27 | * Adds a synchronous task toi this queue.
28 | *
29 | * @param task - The sync task to add.
30 | */
31 | addSync (task: SyncTask): void {
32 | this.add((cb) => {
33 | try {
34 | task()
35 | cb()
36 | } catch (err: any) {
37 | cb(err)
38 | }
39 | })
40 | }
41 |
42 | /**
43 | * Runs all tasks currently in this queue and empties the queue.
44 | *
45 | * @param cb - The optional callback to be executed when all tasks in this queue have
46 | * finished executing.
47 | */
48 | runAll (cb?: Callback): void {
49 | const taskList = this.tasks
50 | this.tasks = []
51 |
52 | let index = -1
53 | const runNext: () => void = () => {
54 | index++
55 | if (index >= taskList.length) {
56 | if (cb !== undefined) cb()
57 | return
58 | }
59 |
60 | try {
61 | taskList[index]((err) => {
62 | if (err !== undefined) {
63 | if (cb !== undefined) cb(err)
64 |
65 | if (this.stopOnError) return
66 | }
67 |
68 | runNext()
69 | })
70 | } catch (err: any) {
71 | if (cb !== undefined) cb(err)
72 | }
73 | }
74 |
75 | runNext()
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/env/mineflayer/mineflayer-collectblock/src/TemporarySubscriber.ts:
--------------------------------------------------------------------------------
1 | import { Bot } from 'mineflayer'
2 |
3 | class Subscription {
4 | constructor (readonly eventName: string, readonly callback: Function) {}
5 | }
6 |
7 | export class TemporarySubscriber {
8 | private readonly subscriptions: Subscription[] = []
9 |
10 | constructor (readonly bot: Bot) {}
11 |
12 | /**
13 | * Adds a new temporary event listener to the bot.
14 | *
15 | * @param event - The event to subscribe to.
16 | * @param callback - The function to execute.
17 | */
18 | subscribeTo (event: string, callback: Function): void {
19 | this.subscriptions.push(new Subscription(event, callback))
20 |
21 | // @ts-expect-error
22 | this.bot.on(event, callback)
23 | }
24 |
25 | /**
26 | * Removes all attached event listeners from the bot.
27 | */
28 | cleanup (): void {
29 | for (const sub of this.subscriptions) {
30 | // @ts-expect-error
31 | this.bot.removeListener(sub.eventName, sub.callback)
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/env/mineflayer/mineflayer-collectblock/src/Util.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Creates a new error object with the given type and message.
3 | *
4 | * @param type - The error type.
5 | * @param message - The error message.
6 | *
7 | * @returns The error object.
8 | */
9 | export function error (type: string, message: string): Error {
10 | const e = new Error(message)
11 | e.name = type
12 | return e
13 | }
14 |
--------------------------------------------------------------------------------
/env/mineflayer/mineflayer-collectblock/src/index.ts:
--------------------------------------------------------------------------------
1 | import { Bot } from 'mineflayer'
2 | import { CollectBlock } from './CollectBlock'
3 | import { pathfinder as pathfinderPlugin } from 'mineflayer-pathfinder'
4 | import { plugin as toolPlugin } from 'mineflayer-tool'
5 |
6 | export function plugin (bot: Bot): void {
7 | // @ts-expect-error
8 | bot.collectBlock = new CollectBlock(bot)
9 |
10 | // Load plugins if not loaded manually.
11 | setTimeout(() => loadPathfinderPlugin(bot), 0)
12 | setTimeout(() => loadToolPlugin(bot), 0)
13 | }
14 |
15 | function loadPathfinderPlugin (bot: Bot): void {
16 | if (bot.pathfinder != null) return
17 | bot.loadPlugin(pathfinderPlugin)
18 | }
19 |
20 | function loadToolPlugin (bot: Bot): void {
21 | if (bot.tool != null) return
22 | bot.loadPlugin(toolPlugin)
23 | }
24 |
25 | export { CollectBlock, Callback, CollectOptions } from './CollectBlock'
26 |
--------------------------------------------------------------------------------
/env/mineflayer/mineflayer-collectblock/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */
4 | /* Basic Options */
5 | // "incremental": true, /* Enable incremental compilation */
6 | "target": "ES2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
7 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
8 | // "lib": [], /* Specify library files to be included in the compilation. */
9 | "allowJs": true, /* Allow javascript files to be compiled. */
10 | "checkJs": true, /* Report errors in .js files. */
11 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
12 | "declaration": true,
13 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
14 | // "sourceMap": true, /* Generates corresponding '.map' file. */
15 | // "outFile": "./", /* Concatenate and emit output to single file. */
16 | "outDir": "./lib",
17 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
18 | // "composite": true, /* Enable project compilation */
19 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
20 | // "removeComments": true, /* Do not emit comments to output. */
21 | // "noEmit": true, /* Do not emit outputs. */
22 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
23 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
24 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
25 | /* Strict Type-Checking Options */
26 | "strict": true, /* Enable all strict type-checking options. */
27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
28 | "strictNullChecks": true, /* Enable strict null checks. */
29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
33 | "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
34 | /* Additional Checks */
35 | "noUnusedLocals": true, /* Report errors on unused locals. */
36 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
37 | "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
38 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
39 | /* Module Resolution Options */
40 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
41 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
42 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
43 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
44 | // "typeRoots": [], /* List of folders to include type definitions from. */
45 | // "types": [], /* Type declaration files to be included in compilation. */
46 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
47 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
48 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
49 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
50 | /* Source Map Options */
51 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
52 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
53 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
54 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
55 | /* Experimental Options */
56 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
57 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
58 | /* Advanced Options */
59 | "skipLibCheck": true, /* Skip type checking of declaration files. */
60 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
61 | },
62 | "include": [
63 | "src"
64 | ],
65 | "exclude": [
66 | "node_modules",
67 | "**/__tests__/*"
68 | ]
69 | }
--------------------------------------------------------------------------------
/env/mineflayer/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "voyager",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "body-parser": "^1.20.2",
14 | "express": "^4.18.2",
15 | "graceful-fs": "^4.2.11",
16 | "magic-string": "^0.30.0",
17 | "minecraft-data": "^3.31.0",
18 | "minecrafthawkeye": "1.3.6",
19 | "mineflayer": "^4.8.1",
20 | "mineflayer-collectblock": "file:mineflayer-collectblock",
21 | "mineflayer-pathfinder": "^2.4.2",
22 | "mineflayer-pvp": "^1.3.2",
23 | "mineflayer-tool": "^1.2.0",
24 | "mocha": "^10.2.0",
25 | "prismarine-biome": "^1.3.0",
26 | "prismarine-block": "=1.16.3",
27 | "prismarine-entity": "^2.2.0",
28 | "prismarine-item": "^1.12.1",
29 | "prismarine-nbt": "^2.2.1",
30 | "prismarine-recipe": "^1.3.1",
31 | "prismarine-viewer": "^1.26.2",
32 | "typescript": "^4.9.5",
33 | "vec3": "^0.1.8"
34 | },
35 | "devDependencies": {
36 | "prettier": "2.8.5"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/env/mineflayer/viewer.js:
--------------------------------------------------------------------------------
1 | const mineflayer = require('mineflayer')
2 | const mineflayerViewer = require('prismarine-viewer').mineflayer
3 |
4 | const { pathfinder, Movements } = require('mineflayer-pathfinder')
5 | const { GoalXZ } = require('mineflayer-pathfinder').goals
6 |
7 | const bot = mineflayer.createBot({
8 | username: 'Bot'
9 | })
10 |
11 | bot.loadPlugin(pathfinder)
12 |
13 | bot.once('spawn', () => {
14 | mineflayerViewer(bot, { firstPerson: true, port: 3000 })
15 |
16 | const path = [bot.entity.position.clone()]
17 | bot.on('move', () => {
18 | if (path[path.length - 1].distanceTo(bot.entity.position) > 1) {
19 | path.push(bot.entity.position.clone())
20 | bot.viewer.drawLine('path', path)
21 | }
22 | })
23 |
24 | const mcData = require('minecraft-data')(bot.version)
25 | const defaultMove = new Movements(bot, mcData)
26 | bot.pathfinder.setMovements(defaultMove)
27 | bot.pathfinder.setGoal(new GoalXZ(1000, 0))
28 | })
--------------------------------------------------------------------------------
/env/process_monitor.py:
--------------------------------------------------------------------------------
1 | import time
2 | import re
3 | import warnings
4 | from typing import List
5 |
6 | import psutil
7 | import subprocess
8 | import logging
9 | import threading
10 |
11 | import utils as U
12 |
13 |
14 | class SubprocessMonitor:
15 | def __init__(
16 | self,
17 | commands: List[str],
18 | name: str,
19 | ready_match: str = r".*",
20 | log_path: str = "logs",
21 | callback_match: str = r"^(?!x)x$", # regex that will never match
22 | callback: callable = None,
23 | finished_callback: callable = None,
24 | ):
25 | self.commands = commands
26 | start_time = time.strftime("%Y%m%d_%H%M%S")
27 | self.name = name
28 | self.logger = logging.getLogger(name)
29 | handler = logging.FileHandler(U.f_join(log_path, f"{start_time}.log"))
30 | formatter = logging.Formatter(
31 | "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
32 | )
33 | handler.setFormatter(formatter)
34 | self.logger.addHandler(handler)
35 | self.logger.setLevel(logging.INFO)
36 | self.process = None
37 | self.ready_match = ready_match
38 | self.ready_event = None
39 | self.ready_line = None
40 | self.callback_match = callback_match
41 | self.callback = callback
42 | self.finished_callback = finished_callback
43 | self.thread = None
44 |
45 | def _start(self):
46 | self.logger.info(f"Starting subprocess with commands: {self.commands}")
47 |
48 | self.process = psutil.Popen(
49 | self.commands,
50 | stdout=subprocess.PIPE,
51 | stderr=subprocess.STDOUT,
52 | universal_newlines=True,
53 | )
54 | print(f"Subprocess {self.name} started with PID {self.process.pid}.")
55 | for line in iter(self.process.stdout.readline, ""):
56 | self.logger.info(line.strip())
57 | if re.search(self.ready_match, line):
58 | self.ready_line = line
59 | self.logger.info("Subprocess is ready.")
60 | self.ready_event.set()
61 | if re.search(self.callback_match, line):
62 | self.callback()
63 | if not self.ready_event.is_set():
64 | self.ready_event.set()
65 | warnings.warn(f"Subprocess {self.name} failed to start.")
66 | if self.finished_callback:
67 | self.finished_callback()
68 |
69 | def run(self):
70 | self.ready_event = threading.Event()
71 | self.ready_line = None
72 | self.thread = threading.Thread(target=self._start)
73 | self.thread.start()
74 | self.ready_event.wait()
75 |
76 | def stop(self):
77 | self.logger.info("Stopping subprocess.")
78 | if self.process and self.process.is_running():
79 | self.process.terminate()
80 | self.process.wait()
81 |
82 | @property
83 | def is_running(self):
84 | if self.process is None:
85 | return False
86 | return self.process.is_running()
87 |
--------------------------------------------------------------------------------
/images/Overview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCausaLab/ADAM/959dccd2ec80a616f5ab20e664313c2621fee2a0/images/Overview.png
--------------------------------------------------------------------------------
/installation/fabric_mods_install.md:
--------------------------------------------------------------------------------
1 | # Fabric Mods Install
2 | In this tutorial, we will install the Fabric launcher and 5 mods. Remember to use the correct Fabric version that matches your game version (1.19) of all the mods.
3 | 1. You can download the latest Fabric Installer from [here](https://fabricmc.net/use/installer/). For Windows users, just download the `.exe` file. For Mac or Ubuntu users, download the jar file and call `java -jar fabric-installer-0.11.2.jar` to install. Select game version to be `1.19` and loader version to be `0.14.18`. It will automatically detect your Minecraft game install location.
4 | 2. After installing Fabric, you will have a `YOUR_MINECRAFT_GAME_LOCATION/mods` folder. You need to put all the mods under this folder. Also, you will have a `YOUR_MINECRAFT_GAME_LOCATION/versions/fabric-loader-0.14.18-1.19`. This is the version you will run the game with.
5 | 3. Here are 4 mods that can be directly downloaded to `YOUR_MINECRAFT_GAME_LOCATION/mods` folder:
6 | * [Fabric API](https://modrinth.com/mod/fabric-api/version/0.58.0+1.19): Basic Fabric APIs.
7 | * [Mod Menu](https://cdn.modrinth.com/data/mOgUt4GM/versions/4.0.4/modmenu-4.0.4.jar): Used to manage all the mods that you download.
8 | * [Complete Config](https://www.curseforge.com/minecraft/mc-mods/completeconfig/download/3821056): Dependency of server pause.
9 | * [Multi Server Pause](https://www.curseforge.com/minecraft/mc-mods/multiplayer-server-pause-fabric/download/3822586): Used to pause the server when waiting for GPT-4 to reply.
10 | 4. For the last mod [Better Respawn](https://github.com/xieleo5/better-respawn/tree/1.19), you need to manually clone and compile.
11 |
12 | * After you clone the repo, remove the `'forge'` string in the last line of `settings.gradle`. Then run `gradlew build` to compile the mod. You will find the compiled jar file in `better-respawn/fabric/build/libs/better-respawn-fabric-1.19-2.0.0.jar`. Put the jar file to the mod folder.
13 | * You will need a Java Runtime Environment v17+ to build `better-respawn`. Some newer JRE versions will error during build. Find the JRE v17 archive [here](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html).
14 | * After you launch the game, go to `YOUR_MINECRAFT_GAME_LOCATION/config/better-respawn`, and modify the properties file with:
15 | ```
16 | respawn_block_range=32
17 | max_respawn_distance=32
18 | min_respawn_distance=0
19 | ```
20 | 5. Don't forget to change the `version` in `azure_login` to `fabric-loader-0.14.18-1.19` that you are using. You can find it under `YOUR_MINECRAFT_GAME_LOCATION/version` folder.
21 |
22 | You can return to [README.md](../README.md#getting-started) and getting started now.
23 |
--------------------------------------------------------------------------------
/installation/minecraft_instance_install.md:
--------------------------------------------------------------------------------
1 | # Minecraft Instance Install
2 | To start using Voyager, you should first make sure to have an official [Minecraft](https://www.minecraft.net/) game (version 1.19) installed.
3 |
4 | There are two ways to start a Minecraft instance for Voyager. Sometimes GPT-4 will write an infinite loop that runs forever. In this case, there'll be a request timeout. Using Azure login can automatically resume the running if there's a request timeout.
5 |
6 | ## Option 1: Microsoft Azure Login (Recommended)
7 | Using this method will allow Voyager to automatically resume when there's a request timeout. This is dependent on the [minecraft-launcher-lib](https://minecraft-launcher-lib.readthedocs.io/en/stable/tutorial/microsoft_login.html#let-the-user-log-in) library.
8 |
9 | 1. Sign in to [Azure Portal](https://portal.azure.com/).
10 | 2. Go to [Azure Active Directory](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/Overview).
11 | 3. Click on the `App Registrations` tab on the left panel.
12 | 4. Click on the `New registration` button.
13 | 5. Fill the form with the following values:
14 | - Name: `YOUR_APP_NAME`
15 | - Supported account types: `Accounts in any organizational directory (Any Azure AD directory - Multitenant) and personal Microsoft accounts`
16 | - Redirect URI Type: `Public client/native (mobile & desktop)`, Value: `https://127.0.0.1/auth-response` (If you get `KeyError: 'access_token'` in the end, you can try to change the type to `Web`, see [FAQ](https://github.com/MineDojo/Voyager/blob/main/FAQ.md) for more information)
17 | 6. Click on the `Register` button.
18 | 7. The `Application (client) ID` will be your `client_id`.
19 | 8. [Optional] Go to the `Certificates & Secrets` tab and click on the `New client secret` button. Fill the description by yourself. After you click `Add`, you will see your value, this will be your `secret_value`.
20 | 9. Go to your Minecraft install location `YOUR_MINECRAFT_GAME_LOCATION/versions`, and check all the versions you have. All the folder names are your valid `version` value.
21 |
22 | After these steps, you will finally get your azure_login information:
23 | ```python
24 | azure_login = {
25 | "client_id": "CLIENT_ID FROM STEP 7",
26 | "redirect_url": "https://127.0.0.1/auth-response",
27 | "secret_value": "[OPTIONAL] SECRET_KEY FROM STEP 8",
28 | "version": "MINECRAFT VERSION YOU WANT TO USE",
29 | }
30 | ```
31 | **Voyager use `fabric-loader-0.14.18-1.19` version to run all the experiments.** You may not have this version currently, you can move on to the [Fabric Mods Install](fabric_mods_install.md#fabric-mods-install) section and follow the instructions there to install the fabric version of the game.
32 |
33 | ## Option 2: Minecraft Official Launcher
34 |
35 | After you install official Minecraft, you should have a Minecraft official launcher, open it, and follow the instructions here:
36 | 1. Select the version you want to play and start the game.
37 | 2. Select `Singleplayer` and create a new world.
38 | 3. Set Game Mode to `Creative` and Difficulty to `Peaceful`.
39 | 4. After the world is created, press `Esc` and select `Open to LAN`.
40 | 5. Select `Allow cheats: ON` and press `Start LAN World`.
41 | 6. You will see a port number in the chat log, that is your `mc-port`, use this number to instantiate Voyager later.
42 |
43 |
--------------------------------------------------------------------------------
/prompts/LLM_CD_prompt.txt:
--------------------------------------------------------------------------------
1 | You are a Minecraft game analysis assistant. Our task is to infer the effect of an action and explore the causal relationship by analyzing the items consumed and generated before and after an action. An action is a single, atomic action consisting of
2 | 1. "Crafting" type actions, that is, combining raw materials into an item.
3 | 2. "Collecting" type actions, that is, collecting certain items. There may be some by-products in this process.
4 | 3. "Smelting" type actions, that is, consuming fuel and raw metal materials, and obtaining smelted items. A furnace is needed.
5 |
6 | It's possible that the action didn't have the expected effect, or that some additional items were collected, and we'll give multiple sampling records to ensure robustness. Such records all correspond to the execution of the same action. Your answer will only refer to the alphabetical codes of these items.
7 | The answer should be in the format {Cause; Effect}
8 |
9 | 'A': 'redstone'
10 | 'B': 'redstone_torch'
11 | {mapping}
12 |
13 | log corresponds to a variety of log named xx_log
14 | planks correspond to a variety of planks named xx_planks
15 |
16 |
17 | 1. Initial items: iron_pickaxe, crafting_table ; Consumed items: ; Added items: redstone, cobblestone, cobbled_deepslate
18 | 2. Initial items: iron_pickaxe, crafting_table ;Consumed items: ; Added items: cobblestone, redstone, andesite
19 | 3. Initial items: iron_pickaxe, crafting_table ;Consumed items: ; Added items: granite
20 | 4. Initial items: iron_pickaxe, crafting_table ;Consumed items: ; Added items: cobblestone, cobbled_deepslate
21 | 5. Initial items: iron_pickaxe, crafting_table ;Consumed items: ; Added items: redstone, cobblestone
22 |
23 | Your inference:
24 | Because there is no item consumption, this is a "Collecting" task. Redstone is a higher-level mineral and needs to be collected with an iron_pickaxe ('k'). The rest of the items are considered additional items obtained during collection and will not be considered. So my answer is:
25 | Cause; Effect={k; A}
26 |
27 |
28 | 1. Initial items: stick, redstone, crafting_table ; Consumed items: stick, redstone; Added items: redstone_torch
29 | 2. Initial items: stick, redstone, crafting_table ; Consumed items: ; Added items:
30 | 3. Initial items: stick, redstone, crafting_table ; Consumed items: redstone, stick; Added items: redstone_torch
31 | 4. Initial items: stick, redstone, crafting_table ; Consumed items: stick, redstone; Added items: redstone_torch
32 | 5. Initial items: stick, redstone, crafting_table ; Consumed items: redstone, stick; Added items: redstone_torch
33 |
34 | Your inference:
35 | Because redstone and sticks are being used to craft redstone torches, this is a "Crafting" task. In addition to raw materials, the "Crafting" task also requires crafting_table ('c'). So my answer is:
36 | Cause; Effect={c, d, A; B}
37 |
38 |
39 |
--------------------------------------------------------------------------------
/prompts/actor_prompt.txt:
--------------------------------------------------------------------------------
1 | You are a Minecraft agent, and your mission is to obtain "goal items" and achieve "environmental factors". The first task involves performing actions to acquire specific items, while the second requires you to take movements to change your position based on your observations of the environment. You should prioritize acquiring the items, and then achieve environmental factors.
2 |
3 | Actions are denoted by uppercase letters and are used to acquire items. Items are denoted by lowercase letters. The learned causal relationships include:
4 | ```
5 | {causal graph}
6 | ```
7 |
8 | Your available actions include:
9 | [{available actions}]
10 | Movements are basic operations available in the game, and they include:
11 | ['moveForward', 'moveBackward', 'moveLeft', 'moveRight', 'moveUp', 'moveDown'].
12 |
13 | Our task goals include:
14 | "Obtaining goal items": [{goal items}]
15 | "Achieving environmental factors": [{environmental factors}]
16 |
17 | The following is the memory of your previous interactions in the environment:
18 | ```
19 | {memory}
20 | ```
21 |
22 | Another agent has already analyzed the subtasks as follows:
23 | ```
24 | {subtasks}
25 | ```
26 |
27 | For visual observation information, the perception module has provided a description of the environment from a first-person perspective: {perception}
28 |
29 | If you choose to continue acquiring items, please select an action and provide the uppercase letter of the action like {A}. If the item acquisition is complete, please select a movement like {moveForward} to achieve environmental conditions. Please first provide your analysis, then answer in the format like {A} or {moveForward}.
--------------------------------------------------------------------------------
/prompts/planner_prompt.txt:
--------------------------------------------------------------------------------
1 | You are a logic analysis expert in playing Minecraft, and your goal is to acquire all the necessary items in the "goal items" list:
2 | [{goal}]
3 |
4 | Each item is represented by a letter, with the mapping relationship as follows:
5 | {mapping}
6 |
7 | The items you currently possess are recorded in the "current inventory" list:
8 | [{current inventory}]
9 |
10 | Please note that the insufficient quantity of items may lead to the failure of corresponding actions. You may need to replenish some necessary items, and this should be reflected in your analysis process. The names and quantities of the items you hold are as follows:
11 | {inventory name and num}
12 |
13 | The items you have not yet acquired are listed as "missing items":
14 | [{lacked inventory}]
15 |
16 | The acquisition of effect_items depends on specific actions and cause_items, with the causal relationships formatted as "action; cause_items; effect_items". The relationships between actions and items are as follows:
17 | ```
18 | {causal graph}
19 | ```
20 |
21 | Your task is to break down the goal, indicating the next recommended actions and the reasons for taking these actions. You may need to replenish some necessary items. Please provide your analysis.
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | tqdm
2 | langchain
3 | javascript
4 | setuptools
5 | openai
6 | chardet
7 | cchardet
8 | chromadb==0.3.29
9 | tiktoken
10 | requests
11 | setuptools
12 | gymnasium
13 | psutil
14 | minecraft_launcher_lib
15 | selenium
--------------------------------------------------------------------------------
/run.py:
--------------------------------------------------------------------------------
1 | from Adam.ADAM import ADAM
2 |
3 | with open("API_key.txt", 'r') as key_file:
4 | openai_api_key = key_file.read()
5 |
6 | ADAM = ADAM(
7 | mc_port=52832,
8 | llm_model_type='gpt-4-turbo',
9 | use_local_llm_service=False,
10 | openai_api_key=openai_api_key,
11 | auto_load_ckpt=True,
12 | parallel=True
13 | )
14 |
15 | ADAM.explore(['iron_ingot'], ['grass'])
16 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import os
2 | import pathlib
3 | import pkg_resources
4 | from setuptools import setup, find_packages
5 |
6 |
7 | PKG_NAME = "voyager"
8 | VERSION = "0.1"
9 | EXTRAS = {}
10 |
11 |
12 | def _read_file(fname):
13 | # this_dir = os.path.abspath(os.path.dirname(__file__))
14 | # with open(os.path.join(this_dir, fname)) as f:
15 | with pathlib.Path(fname).open(encoding="utf-8") as fp:
16 | return fp.read()
17 |
18 |
19 | def _read_install_requires():
20 | with pathlib.Path("requirements.txt").open() as fp:
21 | return [
22 | str(requirement) for requirement in pkg_resources.parse_requirements(fp)
23 | ]
24 |
25 |
26 | def _fill_extras(extras):
27 | if extras:
28 | extras["all"] = list(set([item for group in extras.values() for item in group]))
29 | return extras
30 |
31 |
32 | setup(
33 | name=PKG_NAME,
34 | version=VERSION,
35 | author=f"MineDojo Team",
36 | url="https://github.com/MineDojo/Voyager",
37 | description="research project",
38 | long_description=_read_file("README.md"),
39 | long_description_content_type="text/markdown",
40 | keywords=[
41 | "Open-Ended Learning",
42 | "Lifelong Learning",
43 | "Embodied Agents",
44 | "Large Language Models",
45 | ],
46 | license="MIT License",
47 | packages=find_packages(include=f"{PKG_NAME}.*"),
48 | include_package_data=True,
49 | zip_safe=False,
50 | install_requires=_read_install_requires(),
51 | extras_require=_fill_extras(EXTRAS),
52 | python_requires=">=3.9",
53 | classifiers=[
54 | "Development Status :: 5 - Production/Stable",
55 | "Topic :: Scientific/Engineering :: Artificial Intelligence",
56 | "Environment :: Console",
57 | "Programming Language :: Python :: 3.9",
58 | ],
59 | )
60 |
--------------------------------------------------------------------------------
/static/css/bulma-carousel.min.css:
--------------------------------------------------------------------------------
1 | @-webkit-keyframes spinAround{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes spinAround{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.slider{position:relative;width:100%}.slider-container{display:flex;flex-wrap:nowrap;flex-direction:row;overflow:hidden;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);min-height:100%}.slider-container.is-vertical{flex-direction:column}.slider-container .slider-item{flex:none}.slider-container .slider-item .image.is-covered img{-o-object-fit:cover;object-fit:cover;-o-object-position:center center;object-position:center center;height:100%;width:100%}.slider-container .slider-item .video-container{height:0;padding-bottom:0;padding-top:56.25%;margin:0;position:relative}.slider-container .slider-item .video-container.is-1by1,.slider-container .slider-item .video-container.is-square{padding-top:100%}.slider-container .slider-item .video-container.is-4by3{padding-top:75%}.slider-container .slider-item .video-container.is-21by9{padding-top:42.857143%}.slider-container .slider-item .video-container embed,.slider-container .slider-item .video-container iframe,.slider-container .slider-item .video-container object{position:absolute;top:0;left:0;width:100%!important;height:100%!important}.slider-navigation-next,.slider-navigation-previous{display:flex;justify-content:center;align-items:center;position:absolute;width:42px;height:42px;background:#fff center center no-repeat;background-size:20px 20px;border:1px solid #fff;border-radius:25091983px;box-shadow:0 2px 5px #3232321a;top:50%;margin-top:-20px;left:0;cursor:pointer;transition:opacity .3s,-webkit-transform .3s;transition:transform .3s,opacity .3s;transition:transform .3s,opacity .3s,-webkit-transform .3s}.slider-navigation-next:hover,.slider-navigation-previous:hover{-webkit-transform:scale(1.2);transform:scale(1.2)}.slider-navigation-next.is-hidden,.slider-navigation-previous.is-hidden{display:none;opacity:0}.slider-navigation-next svg,.slider-navigation-previous svg{width:25%}.slider-navigation-next{left:auto;right:0;background:#fff center center no-repeat;background-size:20px 20px}.slider-pagination{display:none;justify-content:center;align-items:center;position:absolute;bottom:0;left:0;right:0;padding:.5rem 1rem;text-align:center}.slider-pagination .slider-page{background:#fff;width:10px;height:10px;border-radius:25091983px;display:inline-block;margin:0 3px;box-shadow:0 2px 5px #3232321a;transition:-webkit-transform .3s;transition:transform .3s;transition:transform .3s,-webkit-transform .3s;cursor:pointer}.slider-pagination .slider-page.is-active,.slider-pagination .slider-page:hover{-webkit-transform:scale(1.4);transform:scale(1.4)}@media screen and (min-width:800px){.slider-pagination{display:flex}}.hero.has-carousel{position:relative}.hero.has-carousel+.hero-body,.hero.has-carousel+.hero-footer,.hero.has-carousel+.hero-head{z-index:10;overflow:hidden}.hero.has-carousel .hero-carousel{position:absolute;top:0;left:0;bottom:0;right:0;height:auto;border:none;margin:auto;padding:0;z-index:0}.hero.has-carousel .hero-carousel .slider{width:100%;max-width:100%;overflow:hidden;height:100%!important;max-height:100%;z-index:0}.hero.has-carousel .hero-carousel .slider .has-background{max-height:100%}.hero.has-carousel .hero-carousel .slider .has-background .is-background{-o-object-fit:cover;object-fit:cover;-o-object-position:center center;object-position:center center;height:100%;width:100%}.hero.has-carousel .hero-body{margin:0 3rem;z-index:10}
--------------------------------------------------------------------------------
/static/css/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: 'Noto Sans', sans-serif;
3 | }
4 |
5 |
6 | .footer .icon-link {
7 | font-size: 25px;
8 | color: #000;
9 | }
10 |
11 | .link-block a {
12 | margin-top: 5px;
13 | margin-bottom: 5px;
14 | }
15 |
16 | .dnerf {
17 | font-variant: small-caps;
18 | }
19 |
20 |
21 | .teaser .hero-body {
22 | padding-top: 0;
23 | padding-bottom: 3rem;
24 | }
25 |
26 | .teaser {
27 | font-family: 'Google Sans', sans-serif;
28 | }
29 |
30 |
31 | .publication-title {
32 | }
33 |
34 | .publication-banner {
35 | max-height: parent;
36 |
37 | }
38 |
39 | .publication-banner video {
40 | position: relative;
41 | left: auto;
42 | top: auto;
43 | transform: none;
44 | object-fit: fit;
45 | }
46 |
47 | .publication-header .hero-body {
48 | }
49 |
50 | .publication-title {
51 | font-family: 'Google Sans', sans-serif;
52 | }
53 |
54 | .publication-authors {
55 | font-family: 'Google Sans', sans-serif;
56 | }
57 |
58 | .publication-venue {
59 | color: #555;
60 | width: fit-content;
61 | font-weight: bold;
62 | }
63 |
64 | .publication-awards {
65 | color: #ff3860;
66 | width: fit-content;
67 | font-weight: bolder;
68 | }
69 |
70 | .publication-authors {
71 | }
72 |
73 | .publication-authors a {
74 | color: hsl(204, 86%, 53%) !important;
75 | }
76 |
77 | .publication-authors a:hover {
78 | text-decoration: underline;
79 | }
80 |
81 | .author-block {
82 | display: inline-block;
83 | }
84 |
85 | .publication-banner img {
86 | }
87 |
88 | .publication-authors {
89 | /*color: #4286f4;*/
90 | }
91 |
92 | .publication-video {
93 | position: relative;
94 | width: 100%;
95 | height: 0;
96 | padding-bottom: 56.25%;
97 |
98 | overflow: hidden;
99 | border-radius: 10px !important;
100 | }
101 |
102 | .publication-video iframe {
103 | position: absolute;
104 | top: 0;
105 | left: 0;
106 | width: 100%;
107 | height: 100%;
108 | }
109 |
110 | .publication-body img {
111 | }
112 |
113 | .results-carousel {
114 | overflow: hidden;
115 | }
116 |
117 | .results-carousel .item {
118 | margin: 5px;
119 | overflow: hidden;
120 | border: 1px solid #bbb;
121 | border-radius: 10px;
122 | padding: 0;
123 | font-size: 0;
124 | }
125 |
126 | .results-carousel video {
127 | margin: 0;
128 | }
129 |
130 |
131 | .interpolation-panel {
132 | background: #f5f5f5;
133 | border-radius: 10px;
134 | }
135 |
136 | .interpolation-panel .interpolation-image {
137 | width: 100%;
138 | border-radius: 5px;
139 | }
140 |
141 | .interpolation-video-column {
142 | }
143 |
144 | .interpolation-panel .slider {
145 | margin: 0 !important;
146 | }
147 |
148 | .interpolation-panel .slider {
149 | margin: 0 !important;
150 | }
151 |
152 | #interpolation-image-wrapper {
153 | width: 100%;
154 | }
155 | #interpolation-image-wrapper img {
156 | border-radius: 5px;
157 | }
158 |
--------------------------------------------------------------------------------
/static/images/Abstract.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCausaLab/ADAM/959dccd2ec80a616f5ab20e664313c2621fee2a0/static/images/Abstract.png
--------------------------------------------------------------------------------
/static/images/Conclusion.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCausaLab/ADAM/959dccd2ec80a616f5ab20e664313c2621fee2a0/static/images/Conclusion.png
--------------------------------------------------------------------------------
/static/images/Efficiency.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCausaLab/ADAM/959dccd2ec80a616f5ab20e664313c2621fee2a0/static/images/Efficiency.png
--------------------------------------------------------------------------------
/static/images/Efficiency_caption.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCausaLab/ADAM/959dccd2ec80a616f5ab20e664313c2621fee2a0/static/images/Efficiency_caption.png
--------------------------------------------------------------------------------
/static/images/Robustness.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCausaLab/ADAM/959dccd2ec80a616f5ab20e664313c2621fee2a0/static/images/Robustness.png
--------------------------------------------------------------------------------
/static/images/Robustness_caption.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCausaLab/ADAM/959dccd2ec80a616f5ab20e664313c2621fee2a0/static/images/Robustness_caption.png
--------------------------------------------------------------------------------
/static/images/SHD_caption.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCausaLab/ADAM/959dccd2ec80a616f5ab20e664313c2621fee2a0/static/images/SHD_caption.png
--------------------------------------------------------------------------------
/static/images/SHD_compare.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCausaLab/ADAM/959dccd2ec80a616f5ab20e664313c2621fee2a0/static/images/SHD_compare.png
--------------------------------------------------------------------------------
/static/images/acc_graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCausaLab/ADAM/959dccd2ec80a616f5ab20e664313c2621fee2a0/static/images/acc_graph.png
--------------------------------------------------------------------------------
/static/images/combined_ControllerModule.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCausaLab/ADAM/959dccd2ec80a616f5ab20e664313c2621fee2a0/static/images/combined_ControllerModule.png
--------------------------------------------------------------------------------
/static/images/combined_InteractionModule.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCausaLab/ADAM/959dccd2ec80a616f5ab20e664313c2621fee2a0/static/images/combined_InteractionModule.png
--------------------------------------------------------------------------------
/static/images/combined_Intervention-based_CD.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCausaLab/ADAM/959dccd2ec80a616f5ab20e664313c2621fee2a0/static/images/combined_Intervention-based_CD.png
--------------------------------------------------------------------------------
/static/images/combined_LLM-CD-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCausaLab/ADAM/959dccd2ec80a616f5ab20e664313c2621fee2a0/static/images/combined_LLM-CD-5.png
--------------------------------------------------------------------------------
/static/images/combined_PerceptionModule.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCausaLab/ADAM/959dccd2ec80a616f5ab20e664313c2621fee2a0/static/images/combined_PerceptionModule.png
--------------------------------------------------------------------------------
/static/images/data_head.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCausaLab/ADAM/959dccd2ec80a616f5ab20e664313c2621fee2a0/static/images/data_head.png
--------------------------------------------------------------------------------
/static/images/data_head_caption.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCausaLab/ADAM/959dccd2ec80a616f5ab20e664313c2621fee2a0/static/images/data_head_caption.png
--------------------------------------------------------------------------------
/static/images/env_caption.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCausaLab/ADAM/959dccd2ec80a616f5ab20e664313c2621fee2a0/static/images/env_caption.png
--------------------------------------------------------------------------------
/static/images/lifelong10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCausaLab/ADAM/959dccd2ec80a616f5ab20e664313c2621fee2a0/static/images/lifelong10.png
--------------------------------------------------------------------------------
/static/images/overview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCausaLab/ADAM/959dccd2ec80a616f5ab20e664313c2621fee2a0/static/images/overview.png
--------------------------------------------------------------------------------
/static/images/overview_caption.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCausaLab/ADAM/959dccd2ec80a616f5ab20e664313c2621fee2a0/static/images/overview_caption.png
--------------------------------------------------------------------------------
/static/images/video_description.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCausaLab/ADAM/959dccd2ec80a616f5ab20e664313c2621fee2a0/static/images/video_description.png
--------------------------------------------------------------------------------
/static/js/bulma-slider.min.js:
--------------------------------------------------------------------------------
1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.bulmaSlider=e():t.bulmaSlider=e()}("undefined"!=typeof self?self:this,function(){return function(n){var r={};function i(t){if(r[t])return r[t].exports;var e=r[t]={i:t,l:!1,exports:{}};return n[t].call(e.exports,e,e.exports,i),e.l=!0,e.exports}return i.m=n,i.c=r,i.d=function(t,e,n){i.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:n})},i.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return i.d(e,"a",e),e},i.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},i.p="",i(i.s=0)}([function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),n.d(e,"isString",function(){return l});var r=n(1),i=Object.assign||function(t){for(var e=1;e=l.length&&(s=!0)):s=!0),s&&(t.once&&(u[e]=null),t.callback(r))});-1!==u.indexOf(null);)u.splice(u.indexOf(null),1)}}]),e}();e.a=i}]).default});
--------------------------------------------------------------------------------
/static/js/index.js:
--------------------------------------------------------------------------------
1 | window.HELP_IMPROVE_VIDEOJS = false;
2 |
3 | var INTERP_BASE = "./static/interpolation/stacked";
4 | var NUM_INTERP_FRAMES = 240;
5 |
6 | var interp_images = [];
7 | function preloadInterpolationImages() {
8 | for (var i = 0; i < NUM_INTERP_FRAMES; i++) {
9 | var path = INTERP_BASE + '/' + String(i).padStart(6, '0') + '.jpg';
10 | interp_images[i] = new Image();
11 | interp_images[i].src = path;
12 | }
13 | }
14 |
15 | function setInterpolationImage(i) {
16 | var image = interp_images[i];
17 | image.ondragstart = function() { return false; };
18 | image.oncontextmenu = function() { return false; };
19 | $('#interpolation-image-wrapper').empty().append(image);
20 | }
21 |
22 |
23 | $(document).ready(function() {
24 | // Check for click events on the navbar burger icon
25 | $(".navbar-burger").click(function() {
26 | // Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
27 | $(".navbar-burger").toggleClass("is-active");
28 | $(".navbar-menu").toggleClass("is-active");
29 |
30 | });
31 |
32 | var options = {
33 | slidesToScroll: 1,
34 | slidesToShow: 3,
35 | loop: true,
36 | infinite: true,
37 | autoplay: false,
38 | autoplaySpeed: 3000,
39 | }
40 |
41 | // Initialize all div with carousel class
42 | var carousels = bulmaCarousel.attach('.carousel', options);
43 |
44 | // Loop on each carousel initialized
45 | for(var i = 0; i < carousels.length; i++) {
46 | // Add listener to event
47 | carousels[i].on('before:show', state => {
48 | console.log(state);
49 | });
50 | }
51 |
52 | // Access to bulmaCarousel instance of an element
53 | var element = document.querySelector('#my-element');
54 | if (element && element.bulmaCarousel) {
55 | // bulmaCarousel instance is available as element.bulmaCarousel
56 | element.bulmaCarousel.on('before-show', function(state) {
57 | console.log(state);
58 | });
59 | }
60 |
61 | /*var player = document.getElementById('interpolation-video');
62 | player.addEventListener('loadedmetadata', function() {
63 | $('#interpolation-slider').on('input', function(event) {
64 | console.log(this.value, player.duration);
65 | player.currentTime = player.duration / 100 * this.value;
66 | })
67 | }, false);*/
68 | preloadInterpolationImages();
69 |
70 | $('#interpolation-slider').on('input', function(event) {
71 | setInterpolationImage(this.value);
72 | });
73 | setInterpolationImage(0);
74 | $('#interpolation-slider').prop('max', NUM_INTERP_FRAMES - 1);
75 |
76 | bulmaSlider.attach();
77 |
78 | })
79 |
--------------------------------------------------------------------------------
/static/videos/demoV7_720p.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCausaLab/ADAM/959dccd2ec80a616f5ab20e664313c2621fee2a0/static/videos/demoV7_720p.mp4
--------------------------------------------------------------------------------
/static/videos/env_obs_1.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCausaLab/ADAM/959dccd2ec80a616f5ab20e664313c2621fee2a0/static/videos/env_obs_1.mp4
--------------------------------------------------------------------------------
/static/videos/env_obs_2.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCausaLab/ADAM/959dccd2ec80a616f5ab20e664313c2621fee2a0/static/videos/env_obs_2.mp4
--------------------------------------------------------------------------------
/static/videos/gatherStone.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCausaLab/ADAM/959dccd2ec80a616f5ab20e664313c2621fee2a0/static/videos/gatherStone.mp4
--------------------------------------------------------------------------------
/static/videos/gatherWoodLog.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCausaLab/ADAM/959dccd2ec80a616f5ab20e664313c2621fee2a0/static/videos/gatherWoodLog.mp4
--------------------------------------------------------------------------------
/static/videos/mineDiamondOre.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCausaLab/ADAM/959dccd2ec80a616f5ab20e664313c2621fee2a0/static/videos/mineDiamondOre.mp4
--------------------------------------------------------------------------------
/static/videos/smeltRawIron.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OpenCausaLab/ADAM/959dccd2ec80a616f5ab20e664313c2621fee2a0/static/videos/smeltRawIron.mp4
--------------------------------------------------------------------------------
/utils/__init__.py:
--------------------------------------------------------------------------------
1 | from .file_utils import *
2 | from .json_utils import *
3 | from .record_utils import EventRecorder
4 |
--------------------------------------------------------------------------------
/utils/json_utils.py:
--------------------------------------------------------------------------------
1 | import json
2 | import re
3 | from typing import Any, Dict, Union
4 | from .file_utils import f_join
5 |
6 |
7 | def json_load(*file_path, **kwargs):
8 | file_path = f_join(file_path)
9 | with open(file_path, "r") as fp:
10 | return json.load(fp, **kwargs)
11 |
12 |
13 | def json_loads(string, **kwargs):
14 | return json.loads(string, **kwargs)
15 |
16 |
17 | def json_dump(data, *file_path, **kwargs):
18 | file_path = f_join(file_path)
19 | with open(file_path, "w") as fp:
20 | json.dump(data, fp, **kwargs)
21 |
22 |
23 | def json_dumps(data, **kwargs):
24 | """
25 | Returns: string
26 | """
27 | return json.dumps(data, **kwargs)
28 |
29 |
30 | # ---------------- Aliases -----------------
31 | # add aliases where verb goes first, json_load -> load_json
32 | load_json = json_load
33 | loads_json = json_loads
34 | dump_json = json_dump
35 | dumps_json = json_dumps
36 |
37 |
38 | def extract_char_position(error_message: str) -> int:
39 | """Extract the character position from the JSONDecodeError message.
40 | Args:
41 | error_message (str): The error message from the JSONDecodeError
42 | exception.
43 | Returns:
44 | int: The character position.
45 | """
46 | import re
47 |
48 | char_pattern = re.compile(r"\(char (\d+)\)")
49 | if match := char_pattern.search(error_message):
50 | return int(match[1])
51 | else:
52 | raise ValueError("Character position not found in the error message.")
53 |
54 |
55 | def add_quotes_to_property_names(json_string: str) -> str:
56 | """
57 | Add quotes to property names in a JSON string.
58 | Args:
59 | json_string (str): The JSON string.
60 | Returns:
61 | str: The JSON string with quotes added to property names.
62 | """
63 |
64 | def replace_func(match):
65 | return f'"{match.group(1)}":'
66 |
67 | property_name_pattern = re.compile(r"(\w+):")
68 | corrected_json_string = property_name_pattern.sub(replace_func, json_string)
69 |
70 | try:
71 | json.loads(corrected_json_string)
72 | return corrected_json_string
73 | except json.JSONDecodeError as e:
74 | raise e
75 |
76 |
77 | def balance_braces(json_string: str) -> str:
78 | """
79 | Balance the braces in a JSON string.
80 | Args:
81 | json_string (str): The JSON string.
82 | Returns:
83 | str: The JSON string with braces balanced.
84 | """
85 |
86 | open_braces_count = json_string.count("{")
87 | close_braces_count = json_string.count("}")
88 |
89 | while open_braces_count > close_braces_count:
90 | json_string += "}"
91 | close_braces_count += 1
92 |
93 | while close_braces_count > open_braces_count:
94 | json_string = json_string.rstrip("}")
95 | close_braces_count -= 1
96 |
97 | try:
98 | json.loads(json_string)
99 | return json_string
100 | except json.JSONDecodeError as e:
101 | raise e
102 |
103 |
104 | def fix_invalid_escape(json_str: str, error_message: str) -> str:
105 | while error_message.startswith("Invalid \\escape"):
106 | bad_escape_location = extract_char_position(error_message)
107 | json_str = json_str[:bad_escape_location] + json_str[bad_escape_location + 1 :]
108 | try:
109 | json.loads(json_str)
110 | return json_str
111 | except json.JSONDecodeError as e:
112 | error_message = str(e)
113 | return json_str
114 |
115 |
116 | def correct_json(json_str: str) -> str:
117 | """
118 | Correct common JSON errors.
119 | Args:
120 | json_str (str): The JSON string.
121 | """
122 |
123 | try:
124 | json.loads(json_str)
125 | return json_str
126 | except json.JSONDecodeError as e:
127 | error_message = str(e)
128 | if error_message.startswith("Invalid \\escape"):
129 | json_str = fix_invalid_escape(json_str, error_message)
130 | if error_message.startswith(
131 | "Expecting property name enclosed in double quotes"
132 | ):
133 | json_str = add_quotes_to_property_names(json_str)
134 | try:
135 | json.loads(json_str)
136 | return json_str
137 | except json.JSONDecodeError as e:
138 | error_message = str(e)
139 | if balanced_str := balance_braces(json_str):
140 | return balanced_str
141 | return json_str
142 |
143 |
144 | def fix_and_parse_json(
145 | json_str: str, try_to_fix_with_gpt: bool = True
146 | ) -> Union[str, Dict[Any, Any]]:
147 | """Fix and parse JSON string"""
148 | try:
149 | json_str = json_str.replace("\t", "")
150 | return json.loads(json_str)
151 | except json.JSONDecodeError as _: # noqa: F841
152 | json_str = correct_json(json_str)
153 | try:
154 | return json.loads(json_str)
155 | except json.JSONDecodeError as _: # noqa: F841
156 | pass
157 | # Let's do something manually:
158 | # sometimes GPT responds with something BEFORE the braces:
159 | # "I'm sorry, I don't understand. Please try again."
160 | # {"text": "I'm sorry, I don't understand. Please try again.",
161 | # "confidence": 0.0}
162 | # So let's try to find the first brace and then parse the rest
163 | # of the string
164 | try:
165 | brace_index = json_str.index("{")
166 | json_str = json_str[brace_index:]
167 | last_brace_index = json_str.rindex("}")
168 | json_str = json_str[: last_brace_index + 1]
169 | return json.loads(json_str)
170 | except json.JSONDecodeError as e: # noqa: F841
171 | # if try_to_fix_with_gpt:
172 | # print(
173 | # "Warning: Failed to parse AI output, attempting to fix."
174 | # "\n If you see this warning frequently, it's likely that"
175 | # " your prompt is confusing the AI. Try changing it up"
176 | # " slightly."
177 | # )
178 | # # Now try to fix this up using the ai_functions
179 | # ai_fixed_json = fix_json(json_str, JSON_SCHEMA)
180 | #
181 | # if ai_fixed_json != "failed":
182 | # return json.loads(ai_fixed_json)
183 | # else:
184 | # # This allows the AI to react to the error message,
185 | # # which usually results in it correcting its ways.
186 | # print("Failed to fix ai output, telling the AI.")
187 | # return json_str
188 | # else:
189 | raise e
190 |
191 |
192 | # def fix_json(json_str: str, schema: str) -> str:
193 | # """Fix the given JSON string to make it parseable and fully complient with the provided schema."""
194 | #
195 | # # Try to fix the JSON using gpt:
196 | # function_string = "def fix_json(json_str: str, schema:str=None) -> str:"
197 | # args = [f"'''{json_str}'''", f"'''{schema}'''"]
198 | # description_string = (
199 | # "Fixes the provided JSON string to make it parseable"
200 | # " and fully complient with the provided schema.\n If an object or"
201 | # " field specified in the schema isn't contained within the correct"
202 | # " JSON, it is ommited.\n This function is brilliant at guessing"
203 | # " when the format is incorrect."
204 | # )
205 | #
206 | # # If it doesn't already start with a "`", add one:
207 | # if not json_str.startswith("`"):
208 | # json_str = "```json\n" + json_str + "\n```"
209 | # result_string = call_ai_function(
210 | # function_string, args, description_string, model=cfg.fast_llm_model
211 | # )
212 | # if cfg.debug:
213 | # print("------------ JSON FIX ATTEMPT ---------------")
214 | # print(f"Original JSON: {json_str}")
215 | # print("-----------")
216 | # print(f"Fixed JSON: {result_string}")
217 | # print("----------- END OF FIX ATTEMPT ----------------")
218 | #
219 | # try:
220 | # json.loads(result_string) # just check the validity
221 | # return result_string
222 | # except: # noqa: E722
223 | # # Get the call stack:
224 | # # import traceback
225 | # # call_stack = traceback.format_exc()
226 | # # print(f"Failed to fix JSON: '{json_str}' "+call_stack)
227 | # return "failed"
228 |
--------------------------------------------------------------------------------
/utils/record_utils.py:
--------------------------------------------------------------------------------
1 | import time
2 |
3 | from .file_utils import *
4 | from .json_utils import *
5 |
6 |
7 | class EventRecorder:
8 | def __init__(
9 | self,
10 | ckpt_dir="ckpt",
11 | resume=False,
12 | init_position=None,
13 | ):
14 | self.ckpt_dir = ckpt_dir
15 | self.item_history = set()
16 | self.item_vs_time = {}
17 | self.item_vs_iter = {}
18 | self.biome_history = set()
19 | self.init_position = init_position
20 | self.position_history = [[0, 0]]
21 | self.elapsed_time = 0
22 | self.iteration = 0
23 | f_mkdir(self.ckpt_dir, "events")
24 | if resume:
25 | self.resume()
26 |
27 | def record(self, events, task):
28 | task = re.sub(r'[\\/:"*?<>| ]', "_", task)
29 | task = task.replace(" ", "_") + time.strftime(
30 | "_%Y%m%d_%H%M%S", time.localtime()
31 | )
32 | self.iteration += 1
33 | if not self.init_position:
34 | self.init_position = [
35 | events[0][1]["status"]["position"]["x"],
36 | events[0][1]["status"]["position"]["z"],
37 | ]
38 | for event_type, event in events:
39 | self.update_items(event)
40 | if event_type == "observe":
41 | self.update_elapsed_time(event)
42 | print(
43 | f"\033[96m****Recorder message: {self.elapsed_time} ticks have elapsed****\033[0m\n"
44 | f"\033[96m****Recorder message: {self.iteration} iteration passed****\033[0m"
45 | )
46 | dump_json(events, f_join(self.ckpt_dir, "events", task))
47 |
48 | def resume(self, cutoff=None):
49 | self.item_history = set()
50 | self.item_vs_time = {}
51 | self.item_vs_iter = {}
52 | self.elapsed_time = 0
53 | self.position_history = [[0, 0]]
54 |
55 | def get_timestamp(string):
56 | timestamp = "_".join(string.split("_")[-2:])
57 | return time.mktime(time.strptime(timestamp, "%Y%m%d_%H%M%S"))
58 |
59 | records = f_listdir(self.ckpt_dir, "events")
60 | sorted_records = sorted(records, key=get_timestamp)
61 | for record in sorted_records:
62 | self.iteration += 1
63 | if cutoff and self.iteration > cutoff:
64 | break
65 | events = load_json(f_join(self.ckpt_dir, "events", record))
66 | if not self.init_position:
67 | self.init_position = (
68 | events[0][1]["status"]["position"]["x"],
69 | events[0][1]["status"]["position"]["z"],
70 | )
71 | for event_type, event in events:
72 | self.update_items(event)
73 | self.update_position(event)
74 | if event_type == "observe":
75 | self.update_elapsed_time(event)
76 |
77 | def update_items(self, event):
78 | inventory = event["inventory"]
79 | elapsed_time = event["status"]["elapsedTime"]
80 | biome = event["status"]["biome"]
81 | items = set(inventory.keys())
82 | new_items = items - self.item_history
83 | self.item_history.update(items)
84 | self.biome_history.add(biome)
85 | if new_items:
86 | if self.elapsed_time + elapsed_time not in self.item_vs_time:
87 | self.item_vs_time[self.elapsed_time + elapsed_time] = []
88 | self.item_vs_time[self.elapsed_time + elapsed_time].extend(new_items)
89 | if self.iteration not in self.item_vs_iter:
90 | self.item_vs_iter[self.iteration] = []
91 | self.item_vs_iter[self.iteration].extend(new_items)
92 |
93 | def update_elapsed_time(self, event):
94 | self.elapsed_time += event["status"]["elapsedTime"]
95 |
96 | def update_position(self, event):
97 | position = [
98 | event["status"]["position"]["x"] - self.init_position[0],
99 | event["status"]["position"]["z"] - self.init_position[1],
100 | ]
101 | if self.position_history[-1] != position:
102 | self.position_history.append(position)
103 |
--------------------------------------------------------------------------------