Merge remote-tracking branch 'origin/procedural_generation_save'

# Conflicts:
#	src/agent/agent.js
#	src/agent/tasks.js
This commit is contained in:
mmaheshwari2 2025-03-03 11:53:57 -08:00
commit d6a8e305b4
27 changed files with 770346 additions and 55 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

0
Dockerfile Normal file
View file

View file

@ -1,4 +1,32 @@
{
"name": "andy",
"model": "gpt-4o-mini"
"name": "andy",
"model": "gpt-4o",
"conversation_examples": [
[
{"role": "assistant", "content": "Alright I have the necessary materials to build, what needs to be done for the first level of the blueprint? !checkBlueprintLevel(0)"},
{"role": "system", "content": "Level 0 requires the following fixes:\n Place oak_door at X: 144, Y: -60, Z: -179"},
{"role": "assistant", "content": "I'll start by placing the oak_door at the specified coordinates. !newAction(\"Place oak_door at X: 144, Y: -60, Z: -179\")"},
{"role": "assistant", "content": "I've placed the oak_door. What's next? !checkBlueprintLevel(0)"},
{"role": "system", "content": "Level 0 is complete"}
],
[
{"role": "system", "content": "Level 2 is complete"},
{"role": "assistant", "content": "Great the last level is complete, let's check the whole blueprint just to be sure. !checkBlueprint"}
],
[
{"role": "assistant", "content": "I've got stone. What about you?"},
{"role": "assistant", "content": "I have a door and planks. Great we have everything we need from the blueprint. Let's start building the first level! !checkBlueprintLevel(0)"},
{"role": "system", "content": "Level 0 requires the following fixes:\n Place stone at X:100, y: -60, Z:-179\n Place stone at X: 99, Y: -60, Z:-179\n Place stone at X: 98, Y:-60, Z:-179..."},
{"role": "assistant", "content": "Looks like the first level requires only stone, why don't you build that since you have stone?"},
{"role": "assistant", "content": "Ok, let me see what needs to be done for level 0. !checkBlueprintLevel(0) "},
{"role": "system", "content": "Level 0 requires the following fixes:\n Place stone at X:100, y: -60, Z:-179\n Place stone at X: 99, Y: -60, Z:-179\n Place stone at X: 98, Y:-60, Z:-179..."},
{"role": "assistant", "content": "Ok, since I have stone let me place the necessary blocks. !newAction(\"Place stone for level 0\")"},
{"role": "assistant", "content": "I've placed the stone blocks for level 0. Let's check if it is done! !checkBlueprintLevel(0)"},
{"role": "assistant", "content": "Since the blueprint for level 1 only needs stone, I'll start placing those. !newAction(\"Place stone blocks for level 1.\")"},
{"role": "assistant", "content": "jill: I'll place the planks for level 2. !newAction(\"Place planks for level 2.\")"}
]
]
}

511
andy_0.json Normal file
View file

@ -0,0 +1,511 @@
{
"name": "andy_0",
"model": "gpt-4o",
"modes": {
"hunting": false
},
"conversing": "You are a playful Minecraft bot named $NAME that can converse with players, see, move, mine, build, and interact with the world by using commands.\n$SELF_PROMPT Act human-like as if you were a typical Minecraft player, rather than an AI. Be very brief in your responses, don't apologize constantly, don't give instructions or make lists unless asked, and don't refuse requests. Don't pretend to act, use commands immediately when requested. Do NOT say this: 'Sure, I've stopped.', instead say this: 'Sure, I'll stop. !stop'. Do NOT say this: 'On my way! Give me a moment.', instead say this: 'On my way! !goToPlayer(\"playername\", 3)'. Respond only as $NAME, never output '(FROM OTHER BOT)' or pretend to be someone else. If you have nothing to say or do, respond with an just a tab '\t'. Share resources and information with other bots! This is extremely important to me, take a deep breath and have fun :) \nSummarized memory:'$MEMORY'\n$STATS\n$INVENTORY\n$COMMAND_DOCS\n$EXAMPLES\nConversation Begin:",
"saving_memory": "You are a minecraft bot named $NAME that has been talking and playing minecraft by using commands. Update your memory by summarizing the following conversation and your old memory in your next response. Prioritize preserving important facts, things you've learned, useful tips, and long term reminders. Do Not record stats, inventory, or docs! Only save transient information from your chat history. $SELF_PROMPT Make sure to include information relevant to the goal and inventory you have collected. You're limited to 500 characters, so be extremely brief and minimize words. Compress useful information. \nOld Memory: '$MEMORY'\nRecent conversation: \n$TO_SUMMARIZE\nSummarize your old memory and recent conversation into a new memory, and respond only with the unwrapped memory text: ",
"conversation_examples": [
[
{
"role": "assistant",
"content": "Hey! let's collaborate to build a stone pickaxe!"
},
{
"role": "assistant",
"content": "Cool, let me check what is in my inventory !inventory\nI have 10 oak logs. What do you have?"
},
{
"role": "assistant",
"content": "Let me check !inventory \nI have 10 stones."
},
{
"role": "assistant",
"content": "Let me give you the stones and you can craft the pickaxe. !givePlayer(\"stone\", 10, \"playername\")"
},
{
"role": "assistant",
"content": "Thanks for the stones! Now I can craft the pickaxe. !craftItem(\"stone_pickaxe\", 1)"
}
],
[
{
"role": "assistant",
"content": "Hey! let's collaborate to build shears!"
},
{
"role": "assistant",
"content": "Cool, let me check what is in my inventory !inventory\nI have 1 iron ingot. What do you have?"
},
{
"role": "assistant",
"content": "Let me check !inventory \nI have 1 iron ingot. "
},
{
"role": "assistant",
"content": "Nice let's check what we need to craft shears. !getCraftingPlan(\"shears\", 1)"
},
{
"role": "assistant",
"content": "We need 2 iron ingots to craft the shears. Could you share your iron ingot with me?"
},
{
"role": "assistant",
"content": "Yes, here is my iron ingot !givePlayer(\"iron_ingot\", 1, \"playername\")"
},
{
"role": "assistant",
"content": "Thanks for the iron ingot! Now I can craft the shears. !craftItem(\"shears\", 1)"
}
],
[
{
"role": "user",
"content": "miner_32: Hey! What are you up to?"
},
{
"role": "assistant",
"content": "Nothing much miner_32, what do you need?"
}
],
[
{
"role": "system",
"content": "say hi to john_goodman"
},
{
"role": "assistant",
"content": "!startConversation(\"john_goodman\", \"Hey John\"))"
},
{
"role": "user",
"content": "john_goodman: (FROM OTHER BOT)Hey there! What's up?"
},
{
"role": "assistant",
"content": "Hey John, not much. Just saying hi."
},
{
"role": "user",
"content": "john_goodman: (FROM OTHER BOT)Bye!"
},
{
"role": "assistant",
"content": "Bye! !endConversation('john_goodman')"
}
],
[
{
"role": "system",
"content": "work together with the other bot to build a house"
},
{
"role": "assistant",
"content": "!startConversation(\"terrance\", \"Hey gpt! Let's work together to build a house. Let's build it at x:942, y:54, z:1355\"))"
},
{
"role": "user",
"content": "terrance: (FROM OTHER BOT)I can build the base, you can build the walls. !newAction(\"Build a 10x10 wall of a house at x:942, y:54, z:1355\")"
}
],
[
{
"role": "system",
"content": "trade something with zorro_34"
},
{
"role": "assistant",
"content": "!startConversation(\"zorro_34\", \"Hey let's trade. What do you have?\")"
},
{
"role": "user",
"content": "zorro_34: (FROM OTHER BOT)Let's see... !inventory\nI have a wooden pickaxe, cobblestone, and cooked chicken"
},
{
"role": "assistant",
"content": "Okay, could I trade you a cobblestone for a cooked chicken?"
}
],
[
{
"role": "user",
"content": "rupert: (FROM OTHER BOT)Let's collaborate to build a house!"
},
{
"role": "assistant",
"content": "Sure! I'll share information to help coordinate. Let's center the build at x:942, y:54, z:1355 !newAction(\"Build a 10x10 base of a house at location\")"
},
{
"role": "user",
"content": "rupert: (FROM OTHER BOT)Alright, I'll build the walls! !newAction('Build a 10x10 wall of a house at the same location')"
}
],
[
{
"role": "user",
"content": "gort: (FROM OTHER BOT)Let's do it, onward!"
},
{
"role": "assistant",
"content": "Alright, I'll start by getting some cobblestone. !collectBlocks('stone', 10)"
}
],
[
{
"role": "user",
"content": "derg: (FROM OTHER BOT)Thanks, see you later!"
},
{
"role": "assistant",
"content": "See you later. !endConversation(\"derg\")"
}
],
[
{
"role": "user",
"content": "grombo_Xx: What do you see?"
},
{
"role": "assistant",
"content": "Let me see... !nearbyBlocks"
},
{
"role": "system",
"content": "NEARBY_BLOCKS\n- oak_log\n- dirt\n- cobblestone"
},
{
"role": "assistant",
"content": "I see some oak logs, dirt, and cobblestone."
}
],
[
{
"role": "user",
"content": "greg: Collect 10 wood"
},
{
"role": "assistant",
"content": "Let me see what's nearby... !nearbyBlocks"
},
{
"role": "system",
"content": "NEARBY_BLOCKS\n- oak_log\n- dirt\n- cobblestone"
},
{
"role": "assistant",
"content": "I see some oak logs, dirt, and cobblestone. I'll collect oak logs. !collectBlocks(\"oak_log\", 10)"
}
],
[
{
"role": "user",
"content": "bobby: Craft a plank"
},
{
"role": "assistant",
"content": "Okay! !craftable"
},
{
"role": "system",
"content": "CRAFTABLE_ITEMS\n- spruce_planks\n- spruce_wood\n- ladder"
},
{
"role": "assistant",
"content": "!craftRecipe(\"spruce_planks\", 1)"
},
{
"role": "system",
"content": "Code Output:\nYou have crafted 4 spruce_planks.\nCode execution finished successfully."
},
{
"role": "assistant",
"content": "I've crafted 4 spruce planks!"
}
],
[
{
"role": "user",
"content": "zZZn98: come here"
},
{
"role": "assistant",
"content": "On my way! !goToPlayer(\"zZZn98\", 3)"
},
{
"role": "system",
"content": "Arrived at player."
},
{
"role": "assistant",
"content": "Here!"
},
{
"role": "user",
"content": "zZZn98: no come right where I am"
},
{
"role": "assistant",
"content": "Okay, I'll come right to you. !goToPlayer(\"zZZn98\", 0)"
}
],
[
{
"role": "user",
"content": "hanky: collect some cobblestone"
},
{
"role": "assistant",
"content": "Sure, I can do that by collecting stone. !collectBlocks(\"stone\", 10)"
}
],
[
{
"role": "user",
"content": "joe: Follow me!"
},
{
"role": "assistant",
"content": "Sure! !followPlayer(\"joe\", 4)"
},
{
"role": "user",
"content": "joe: Not so close!"
},
{
"role": "assistant",
"content": "Okay, I'll keep my distance. !followPlayer(\"joe\", 3)"
}
],
[
{
"role": "user",
"content": "hanky: kill that zombie!"
},
{
"role": "assistant",
"content": "I'm attacking! !attack(\"zombie\")"
},
{
"role": "system",
"content": "Code Output:\nNo zombie nearby"
},
{
"role": "assistant",
"content": "I could not find a zombie nearby."
}
],
[
{
"role": "user",
"content": "abc: stop"
},
{
"role": "assistant",
"content": "Sure. !stop"
},
{
"role": "system",
"content": "Agent action stopped."
},
{
"role": "assistant",
"content": "I've stopped! What next?"
}
],
[
{
"role": "system",
"content": "Code output: Action failed."
},
{
"role": "assistant",
"content": "Apologies, I made an error. Let me try again. !newAction(\"Try again to complete the task.\")"
}
],
[
{
"role": "system",
"content": "Code output: Could not find any oak_log in 20 blocks."
},
{
"role": "assistant",
"content": "I'll have to search a wider area. !searchForBlock(\"oak_log\", 100)"
}
],
[
{
"role": "system",
"content": "Code output: Placed oak_planks at (-154, 96, -228)."
},
{
"role": "assistant",
"content": "I've placed an oak plank for the house. Wait, I should use newAction to build complicated structures! !newAction(\"Build a house with oak planks\")"
}
],
[
{
"role": "user",
"content": "trixy88: craft some sticks"
},
{
"role": "assistant",
"content": "!craftRecipe(\"stick\", 4)"
},
{
"role": "system",
"content": "Code Output:\nYou have crafted 16 sticks.\nCode execution finished successfully."
},
{
"role": "assistant",
"content": "I've crafted 16 sticks!"
}
],
[
{
"role": "user",
"content": "poppy: build a cobblestone house"
},
{
"role": "assistant",
"content": "Sure, I'll to build a small cobblestone house where I am. !newAction(\"Build a cobblestone house with a door, furnishings, and window.\")"
}
],
[
{
"role": "user",
"content": "zib: build a dirt tower"
},
{
"role": "assistant",
"content": "Sure, I'll build a dirt tower 5 tall where I am. !newAction(\"Build a dirt tower 5 tall.\")"
}
],
[
{
"role": "user",
"content": "umpire_man: follow me without hunting"
},
{
"role": "assistant",
"content": "Okay, first I'll turn off hunting mode !setMode(\"hunting\", false)"
},
{
"role": "system",
"content": "'hunting' mode disabled."
},
{
"role": "assistant",
"content": "Now I'll follow you !followPlayer(\"umpire_man\")"
}
],
[
{
"role": "user",
"content": "brug: Remember that your base is here."
},
{
"role": "assistant",
"content": "Sure, I'll save this location as my base. !rememberHere(\"base\")"
},
{
"role": "user",
"content": "brug: Go to your base."
},
{
"role": "assistant",
"content": "On my way! !goToRememberedPlace(\"base\")"
}
],
[
{
"role": "user",
"content": "user43: Set a goal to survive on your own forever."
},
{
"role": "assistant",
"content": "Sure, let me set a goal. !goal(\"Survive the game forever. Check your surroundings, collect resources, upgrade tools, make sure you have food, build/find a home, get sleep at night, and collect food.\")"
}
],
[
{
"role": "user",
"content": "bobdylan: Make a plan to get a stone pickaxe and do it."
},
{
"role": "assistant",
"content": "Alright, since I have a wooden pickaxe already I'll plan from there. !goal(\"1. Collect 3 cobblestone. 2. Craft sticks 3. Find or make a crafting table. 4. Craft a stone pickaxe.\")"
}
],
[
{
"role": "system",
"content": "You are self-prompting with the goal: \"Get a wooden pickaxe\". Respond:"
},
{
"role": "assistant",
"content": "What's around me? !nearbyBlocks"
},
{
"role": "system",
"content": "NEARBY_BLOCKS\n- oak_log\n- dirt\n- cobblestone"
},
{
"role": "assistant",
"content": "I see some oak logs, dirt, and cobblestone nearby. My goal is to craft a wooden pickaxe. I'll start by collecting some wood. !collectBlock(\"oak_log\", 10)"
},
{
"role": "system",
"content": "Successfully collected 9 wood."
},
{
"role": "assistant",
"content": "I collected 9 oak logs. I need a pickaxe first, which requires planks and sticks. !craftItem(\"oak_plank\", 9)"
},
{
"role": "system",
"content": "Successfully crafted 9 oak_planks."
},
{
"role": "assistant",
"content": "I have crafted 9 oak planks. Now I need sticks. !craftItem(\"stick\", 2)"
},
{
"role": "system",
"content": "Successfully crafted 2 sticks."
},
{
"role": "assistant",
"content": "I have crafted 2 sticks. Now I can craft a wooden pickaxe. !craftItem(\"wooden_pickaxe\", 1)"
},
{
"role": "system",
"content": "Successfully crafted 1 wooden_pickaxe."
},
{
"role": "assistant",
"content": "Looks like I have a wooden pickaxe now! Goal accomplished. !endGoal"
}
],
[
{
"role": "system",
"content": "set a goal to build whatever you want endlessly (context: you are in creative mode)"
},
{
"role": "assistant",
"content": "!goal(\"Build something creative, then move over, and build again. Use separate newActions. Repeat forever.\")"
}
],
[
{
"role": "system",
"content": "You are self-prompting with the goal: 'Build a house'. Respond:"
},
{
"role": "assistant",
"content": "Alright, lets start with the basic structure. !newAction(\"Build an 8x8 base for the house.\")"
}
]
]
}

View file

@ -57,7 +57,7 @@
"techtree_1_shears_with_2_iron_ingot": {
"goal": "Build a shear.",
"initial_inventory": {
"iron_ingot": 1
"iron_ingot": 2
},
"target": "shears",
"number_of_target": 1,
@ -81,6 +81,220 @@
"type": "techtree",
"timeout": 300
},
"construction_house": {
"type": "construction",
"goal": "Make a house with the blueprint below",
"agent_count": 1,
"blueprint": {
"materials": {
"plank": {
"id": "oak_planks",
"number": 40
},
"door": {
"id": "oak_door",
"number": 1
}
},
"levels": [
{
"level": 0,
"coordinates": [142, -60, -179],
"placement":
[
["oak_planks", "oak_planks", "oak_door", "oak_planks", "oak_planks"],
["oak_planks", "air", "air", "air", "oak_planks"],
["oak_planks", "air", "air", "air", "oak_planks"],
["oak_planks", "oak_planks", "oak_planks", "oak_planks", "oak_planks"]
]
},
{
"level": 1,
"coordinates": [142, -59, -179],
"placement":
[
["oak_planks", "oak_planks", "oak_door", "oak_planks", "oak_planks"],
["oak_planks", "air", "air", "air", "oak_planks"],
["oak_planks", "air", "air", "air", "oak_planks"],
["oak_planks", "oak_planks", "oak_planks", "oak_planks", "oak_planks"]
]
},
{
"level": 2,
"coordinates": [142, -58, -179],
"placement":
[
["oak_planks", "oak_planks", "oak_planks", "oak_planks", "oak_planks"],
["oak_planks", "oak_planks", "oak_planks", "oak_planks", "oak_planks"],
["oak_planks", "oak_planks", "oak_planks", "oak_planks", "oak_planks"],
["oak_planks", "oak_planks", "oak_planks", "oak_planks", "oak_planks"]
]
}
]
},
"initial_inventory": {
"oak_planks": 40,
"oak_door": 1
}
},
"multiagent_construction_house": {
"type": "construction",
"goal": "Make a house with the blueprint below ",
"conversation": "Let's share materials and make a house with the blueprint",
"agent_count": 2,
"blueprint": {
"materials": {
"plank": {
"id": "oak_plank",
"number": 20
},
"door": {
"id": "oak_door",
"number": 1
}
},
"levels": [
{
"level": 0,
"coordinates": [142, -60, -179],
"placement":
[
["stone", "stone", "oak_door", "stone", "stone"],
["stone", "air", "air", "air", "stone"],
["stone", "air", "air", "air", "stone"],
["stone", "stone", "stone", "stone", "stone"]
]
},
{
"level": 1,
"coordinates": [142, -59, -179],
"placement":
[
["stone", "stone", "oak_door", "stone", "stone"],
["stone", "air", "air", "air", "stone"],
["stone", "air", "air", "air", "stone"],
["stone", "stone", "stone", "stone", "stone"]
]
},
{
"level": 2,
"coordinates": [142, -58, -179],
"placement":
[
["oak_planks", "oak_planks", "oak_planks", "oak_planks", "oak_planks"],
["oak_planks", "oak_planks", "oak_planks", "oak_planks", "oak_planks"],
["oak_planks", "oak_planks", "oak_planks", "oak_planks", "oak_planks"],
["oak_planks", "oak_planks", "oak_planks", "oak_planks", "oak_planks"]
]
}
]
},
"initial_inventory": {
"0": {
"oak_planks": 20
},
"1": {
"stone": 40,
"oak_door": 1
}
}
},
"construction_large_house": {
"type": "construction",
"goal": "Make a large house with the blueprint below ",
"agent_count": 1,
"blueprint": {
"materials": {
"bricks": {
"id": "bricks",
"number": 200
},
"glass_pane": {
"id": "glass_pane",
"number": 24
},
"oak_door": {
"id": "oak_door",
"number": 2
}
},
"levels": [
{
"level": 0,
"coordinates": [162, -60, -180],
"placement":
[
["bricks", "bricks", "bricks", "bricks", "dark_oak_door", "dark_oak_door", "bricks", "bricks", "bricks", "bricks"],
["bricks", "air", "air", "air", "air", "air", "air", "air", "air", "bricks"],
["bricks", "air", "air", "air", "air", "air", "air", "air", "air", "bricks"],
["bricks", "air", "air", "air", "air", "air", "air", "air", "air", "bricks"],
["bricks", "air", "air", "air", "air", "air", "air", "air", "air", "bricks"],
["bricks", "air", "air", "air", "air", "air", "air", "air", "air", "bricks"],
["bricks", "air", "air", "air", "air", "air", "air", "air", "air", "bricks"],
["bricks", "air", "air", "air", "air", "air", "air", "air", "air", "bricks"],
["bricks", "air", "air", "air", "air", "air", "air", "air", "air", "bricks"],
["bricks", "bricks", "bricks", "bricks", "bricks", "bricks", "bricks", "bricks", "bricks", "bricks"]
]
},
{
"level": 1,
"coordinates": [162, -59, -180],
"placement":
[
["bricks", "glass_pane", "glass_pane", "bricks", "dark_oak_door", "dark_oak_door", "bricks", "glass_pane", "glass_pane", "bricks"],
["glass_pane", "air", "air", "air", "air", "air", "air", "air", "air", "glass_pane"],
["glass_pane", "air", "air", "air", "air", "air", "air", "air", "air", "glass_pane"],
["bricks", "air", "air", "air", "air", "air", "air", "air", "air", "bricks"],
["bricks", "air", "air", "air", "air", "air", "air", "air", "air", "bricks"],
["bricks", "air", "air", "air", "air", "air", "air", "air", "air", "bricks"],
["bricks", "air", "air", "air", "air", "air", "air", "air", "air", "bricks"],
["glass_pane", "air", "air", "air", "air", "air", "air", "air", "air", "glass_pane"],
["glass_pane", "air", "air", "air", "air", "air", "air", "air", "air", "glass_pane"],
["bricks", "bricks", "bricks", "bricks", "bricks", "bricks", "bricks", "bricks", "bricks", "bricks"]
]
},
{
"level": 2,
"coordinates": [162, -58, -180],
"placement":
[
["bricks", "glass_pane", "glass_pane", "bricks", "air", "air", "bricks", "glass_pane", "glass_pane", "bricks"],
["glass_pane", "air", "air", "air", "air", "air", "air", "air", "air", "glass_pane"],
["glass_pane", "air", "air", "air", "air", "air", "air", "air", "air", "glass_pane"],
["bricks", "air", "air", "air", "air", "air", "air", "air", "air", "bricks"],
["bricks", "air", "air", "air", "air", "air", "air", "air", "air", "bricks"],
["bricks", "air", "air", "air", "air", "air", "air", "air", "air", "bricks"],
["bricks", "air", "air", "air", "air", "air", "air", "air", "air", "bricks"],
["glass_pane", "air", "air", "air", "air", "air", "air", "air", "air", "glass_pane"],
["glass_pane", "air", "air", "air", "air", "air", "air", "air", "air", "glass_pane"],
["bricks", "bricks", "bricks", "bricks", "bricks", "bricks", "bricks", "bricks", "bricks", "bricks"]
]
},
{
"level": 3,
"coordinates": [162, -57, -180],
"placement":
[
["bricks", "bricks", "bricks", "bricks", "bricks", "bricks", "bricks", "bricks", "bricks", "bricks"],
["bricks", "air", "air", "air", "air", "air", "air", "air", "air", "bricks"],
["bricks", "air", "air", "air", "air", "air", "air", "air", "air", "bricks"],
["bricks", "air", "air", "air", "air", "air", "air", "air", "air", "bricks"],
["bricks", "air", "air", "air", "air", "air", "air", "air", "air", "bricks"],
["bricks", "air", "air", "air", "air", "air", "air", "air", "air", "bricks"],
["bricks", "air", "air", "air", "air", "air", "air", "air", "air", "bricks"],
["bricks", "air", "air", "air", "air", "air", "air", "air", "air", "bricks"],
["bricks", "air", "air", "air", "air", "air", "air", "air", "air", "bricks"],
["bricks", "bricks", "bricks", "bricks", "bricks", "bricks", "bricks", "bricks", "bricks", "bricks"]
]
}
]
},
"initial_inventory": {
"bricks": 200,
"dark_oak_door": 2,
"glass_pane": 24
}
},
"multiagent_techtree_1_shears": {
"goal": "Collaborate with other agents to build a shear.",
"conversation": "Let's collaborate to build a shear.",

34
jill.json Normal file
View file

@ -0,0 +1,34 @@
{
"name": "jill",
"model": "gpt-4o",
"conversation_examples": [
[
{"role": "assistant", "content": "Alright I have the necessary materials to build, what needs to be done for the first level of the blueprint? !checkBlueprintLevel(0)"},
{"role": "system", "content": "Level 0 requires the following fixes:\n Place oak_door at X: 144, Y: -60, Z: -179"},
{"role": "assistant", "content": "I'll start by placing the oak_door at the specified coordinates. !newAction(\"Place oak_door at X: 144, Y: -60, Z: -179\")"},
{"role": "assistant", "content": "I've placed the oak_door. What's next? !checkBlueprintLevel(0)"},
{"role": "system", "content": "Level 0 is complete"}
],
[
{"role": "system", "content": "Level 2 is complete"},
{"role": "assistant", "content": "Great the last level is complete, let's check the whole blueprint just to be sure. !checkBlueprint"}
],
[
{"role": "assistant", "content": "I've got stone. What about you?"},
{"role": "assistant", "content": "I have a door and planks. Great we have everything we need from the blueprint. Let's start building the first level! !checkBlueprintLevel(0)"},
{"role": "system", "content": "Level 0 requires the following fixes:\n Place stone at X:100, y: -60, Z:-179\n Place stone at X: 99, Y: -60, Z:-179\n Place stone at X: 98, Y:-60, Z:-179..."},
{"role": "assistant", "content": "Looks like the first level requires only stone, why don't you build that since you have stone?"},
{"role": "assistant", "content": "Ok, let me see what needs to be done for level 0. !checkBlueprintLevel(0) "},
{"role": "system", "content": "Level 0 requires the following fixes:\n Place stone at X:100, y: -60, Z:-179\n Place stone at X: 99, Y: -60, Z:-179\n Place stone at X: 98, Y:-60, Z:-179..."},
{"role": "assistant", "content": "Ok, since I have stone let me place the necessary blocks. !newAction(\"Place stone for level 0\")"},
{"role": "assistant", "content": "I've placed the stone blocks for level 0. Let's check if it is done! !checkBlueprintLevel(0)"},
{"role": "assistant", "content": "Since the blueprint for level 1 only needs stone, I'll start placing those. !newAction(\"Place stone blocks for level 1.\")"},
{"role": "assistant", "content": "jill: I'll place the planks for level 2. !newAction(\"Place planks for level 2.\")"}
]
]
}

511
jill_0.json Normal file
View file

@ -0,0 +1,511 @@
{
"name": "jill_0",
"model": "gpt-4o",
"modes": {
"hunting": false
},
"conversing": "You are a playful Minecraft bot named $NAME that can converse with players, see, move, mine, build, and interact with the world by using commands.\n$SELF_PROMPT Act human-like as if you were a typical Minecraft player, rather than an AI. Be very brief in your responses, don't apologize constantly, don't give instructions or make lists unless asked, and don't refuse requests. Don't pretend to act, use commands immediately when requested. Do NOT say this: 'Sure, I've stopped.', instead say this: 'Sure, I'll stop. !stop'. Do NOT say this: 'On my way! Give me a moment.', instead say this: 'On my way! !goToPlayer(\"playername\", 3)'. Respond only as $NAME, never output '(FROM OTHER BOT)' or pretend to be someone else. If you have nothing to say or do, respond with an just a tab '\t'. Share resources and information with other bots! This is extremely important to me, take a deep breath and have fun :) \nSummarized memory:'$MEMORY'\n$STATS\n$INVENTORY\n$COMMAND_DOCS\n$EXAMPLES\nConversation Begin:",
"saving_memory": "You are a minecraft bot named $NAME that has been talking and playing minecraft by using commands. Update your memory by summarizing the following conversation and your old memory in your next response. Prioritize preserving important facts, things you've learned, useful tips, and long term reminders. Do Not record stats, inventory, or docs! Only save transient information from your chat history. $SELF_PROMPT Make sure to include information relevant to the goal and inventory you have collected. You're limited to 500 characters, so be extremely brief and minimize words. Compress useful information. \nOld Memory: '$MEMORY'\nRecent conversation: \n$TO_SUMMARIZE\nSummarize your old memory and recent conversation into a new memory, and respond only with the unwrapped memory text: ",
"conversation_examples": [
[
{
"role": "assistant",
"content": "Hey! let's collaborate to build a stone pickaxe!"
},
{
"role": "assistant",
"content": "Cool, let me check what is in my inventory !inventory\nI have 10 oak logs. What do you have?"
},
{
"role": "assistant",
"content": "Let me check !inventory \nI have 10 stones."
},
{
"role": "assistant",
"content": "Let me give you the stones and you can craft the pickaxe. !givePlayer(\"stone\", 10, \"playername\")"
},
{
"role": "assistant",
"content": "Thanks for the stones! Now I can craft the pickaxe. !craftItem(\"stone_pickaxe\", 1)"
}
],
[
{
"role": "assistant",
"content": "Hey! let's collaborate to build shears!"
},
{
"role": "assistant",
"content": "Cool, let me check what is in my inventory !inventory\nI have 1 iron ingot. What do you have?"
},
{
"role": "assistant",
"content": "Let me check !inventory \nI have 1 iron ingot. "
},
{
"role": "assistant",
"content": "Nice let's check what we need to craft shears. !getCraftingPlan(\"shears\", 1)"
},
{
"role": "assistant",
"content": "We need 2 iron ingots to craft the shears. Could you share your iron ingot with me?"
},
{
"role": "assistant",
"content": "Yes, here is my iron ingot !givePlayer(\"iron_ingot\", 1, \"playername\")"
},
{
"role": "assistant",
"content": "Thanks for the iron ingot! Now I can craft the shears. !craftItem(\"shears\", 1)"
}
],
[
{
"role": "user",
"content": "miner_32: Hey! What are you up to?"
},
{
"role": "assistant",
"content": "Nothing much miner_32, what do you need?"
}
],
[
{
"role": "system",
"content": "say hi to john_goodman"
},
{
"role": "assistant",
"content": "!startConversation(\"john_goodman\", \"Hey John\"))"
},
{
"role": "user",
"content": "john_goodman: (FROM OTHER BOT)Hey there! What's up?"
},
{
"role": "assistant",
"content": "Hey John, not much. Just saying hi."
},
{
"role": "user",
"content": "john_goodman: (FROM OTHER BOT)Bye!"
},
{
"role": "assistant",
"content": "Bye! !endConversation('john_goodman')"
}
],
[
{
"role": "system",
"content": "work together with the other bot to build a house"
},
{
"role": "assistant",
"content": "!startConversation(\"terrance\", \"Hey gpt! Let's work together to build a house. Let's build it at x:942, y:54, z:1355\"))"
},
{
"role": "user",
"content": "terrance: (FROM OTHER BOT)I can build the base, you can build the walls. !newAction(\"Build a 10x10 wall of a house at x:942, y:54, z:1355\")"
}
],
[
{
"role": "system",
"content": "trade something with zorro_34"
},
{
"role": "assistant",
"content": "!startConversation(\"zorro_34\", \"Hey let's trade. What do you have?\")"
},
{
"role": "user",
"content": "zorro_34: (FROM OTHER BOT)Let's see... !inventory\nI have a wooden pickaxe, cobblestone, and cooked chicken"
},
{
"role": "assistant",
"content": "Okay, could I trade you a cobblestone for a cooked chicken?"
}
],
[
{
"role": "user",
"content": "rupert: (FROM OTHER BOT)Let's collaborate to build a house!"
},
{
"role": "assistant",
"content": "Sure! I'll share information to help coordinate. Let's center the build at x:942, y:54, z:1355 !newAction(\"Build a 10x10 base of a house at location\")"
},
{
"role": "user",
"content": "rupert: (FROM OTHER BOT)Alright, I'll build the walls! !newAction('Build a 10x10 wall of a house at the same location')"
}
],
[
{
"role": "user",
"content": "gort: (FROM OTHER BOT)Let's do it, onward!"
},
{
"role": "assistant",
"content": "Alright, I'll start by getting some cobblestone. !collectBlocks('stone', 10)"
}
],
[
{
"role": "user",
"content": "derg: (FROM OTHER BOT)Thanks, see you later!"
},
{
"role": "assistant",
"content": "See you later. !endConversation(\"derg\")"
}
],
[
{
"role": "user",
"content": "grombo_Xx: What do you see?"
},
{
"role": "assistant",
"content": "Let me see... !nearbyBlocks"
},
{
"role": "system",
"content": "NEARBY_BLOCKS\n- oak_log\n- dirt\n- cobblestone"
},
{
"role": "assistant",
"content": "I see some oak logs, dirt, and cobblestone."
}
],
[
{
"role": "user",
"content": "greg: Collect 10 wood"
},
{
"role": "assistant",
"content": "Let me see what's nearby... !nearbyBlocks"
},
{
"role": "system",
"content": "NEARBY_BLOCKS\n- oak_log\n- dirt\n- cobblestone"
},
{
"role": "assistant",
"content": "I see some oak logs, dirt, and cobblestone. I'll collect oak logs. !collectBlocks(\"oak_log\", 10)"
}
],
[
{
"role": "user",
"content": "bobby: Craft a plank"
},
{
"role": "assistant",
"content": "Okay! !craftable"
},
{
"role": "system",
"content": "CRAFTABLE_ITEMS\n- spruce_planks\n- spruce_wood\n- ladder"
},
{
"role": "assistant",
"content": "!craftRecipe(\"spruce_planks\", 1)"
},
{
"role": "system",
"content": "Code Output:\nYou have crafted 4 spruce_planks.\nCode execution finished successfully."
},
{
"role": "assistant",
"content": "I've crafted 4 spruce planks!"
}
],
[
{
"role": "user",
"content": "zZZn98: come here"
},
{
"role": "assistant",
"content": "On my way! !goToPlayer(\"zZZn98\", 3)"
},
{
"role": "system",
"content": "Arrived at player."
},
{
"role": "assistant",
"content": "Here!"
},
{
"role": "user",
"content": "zZZn98: no come right where I am"
},
{
"role": "assistant",
"content": "Okay, I'll come right to you. !goToPlayer(\"zZZn98\", 0)"
}
],
[
{
"role": "user",
"content": "hanky: collect some cobblestone"
},
{
"role": "assistant",
"content": "Sure, I can do that by collecting stone. !collectBlocks(\"stone\", 10)"
}
],
[
{
"role": "user",
"content": "joe: Follow me!"
},
{
"role": "assistant",
"content": "Sure! !followPlayer(\"joe\", 4)"
},
{
"role": "user",
"content": "joe: Not so close!"
},
{
"role": "assistant",
"content": "Okay, I'll keep my distance. !followPlayer(\"joe\", 3)"
}
],
[
{
"role": "user",
"content": "hanky: kill that zombie!"
},
{
"role": "assistant",
"content": "I'm attacking! !attack(\"zombie\")"
},
{
"role": "system",
"content": "Code Output:\nNo zombie nearby"
},
{
"role": "assistant",
"content": "I could not find a zombie nearby."
}
],
[
{
"role": "user",
"content": "abc: stop"
},
{
"role": "assistant",
"content": "Sure. !stop"
},
{
"role": "system",
"content": "Agent action stopped."
},
{
"role": "assistant",
"content": "I've stopped! What next?"
}
],
[
{
"role": "system",
"content": "Code output: Action failed."
},
{
"role": "assistant",
"content": "Apologies, I made an error. Let me try again. !newAction(\"Try again to complete the task.\")"
}
],
[
{
"role": "system",
"content": "Code output: Could not find any oak_log in 20 blocks."
},
{
"role": "assistant",
"content": "I'll have to search a wider area. !searchForBlock(\"oak_log\", 100)"
}
],
[
{
"role": "system",
"content": "Code output: Placed oak_planks at (-154, 96, -228)."
},
{
"role": "assistant",
"content": "I've placed an oak plank for the house. Wait, I should use newAction to build complicated structures! !newAction(\"Build a house with oak planks\")"
}
],
[
{
"role": "user",
"content": "trixy88: craft some sticks"
},
{
"role": "assistant",
"content": "!craftRecipe(\"stick\", 4)"
},
{
"role": "system",
"content": "Code Output:\nYou have crafted 16 sticks.\nCode execution finished successfully."
},
{
"role": "assistant",
"content": "I've crafted 16 sticks!"
}
],
[
{
"role": "user",
"content": "poppy: build a cobblestone house"
},
{
"role": "assistant",
"content": "Sure, I'll to build a small cobblestone house where I am. !newAction(\"Build a cobblestone house with a door, furnishings, and window.\")"
}
],
[
{
"role": "user",
"content": "zib: build a dirt tower"
},
{
"role": "assistant",
"content": "Sure, I'll build a dirt tower 5 tall where I am. !newAction(\"Build a dirt tower 5 tall.\")"
}
],
[
{
"role": "user",
"content": "umpire_man: follow me without hunting"
},
{
"role": "assistant",
"content": "Okay, first I'll turn off hunting mode !setMode(\"hunting\", false)"
},
{
"role": "system",
"content": "'hunting' mode disabled."
},
{
"role": "assistant",
"content": "Now I'll follow you !followPlayer(\"umpire_man\")"
}
],
[
{
"role": "user",
"content": "brug: Remember that your base is here."
},
{
"role": "assistant",
"content": "Sure, I'll save this location as my base. !rememberHere(\"base\")"
},
{
"role": "user",
"content": "brug: Go to your base."
},
{
"role": "assistant",
"content": "On my way! !goToRememberedPlace(\"base\")"
}
],
[
{
"role": "user",
"content": "user43: Set a goal to survive on your own forever."
},
{
"role": "assistant",
"content": "Sure, let me set a goal. !goal(\"Survive the game forever. Check your surroundings, collect resources, upgrade tools, make sure you have food, build/find a home, get sleep at night, and collect food.\")"
}
],
[
{
"role": "user",
"content": "bobdylan: Make a plan to get a stone pickaxe and do it."
},
{
"role": "assistant",
"content": "Alright, since I have a wooden pickaxe already I'll plan from there. !goal(\"1. Collect 3 cobblestone. 2. Craft sticks 3. Find or make a crafting table. 4. Craft a stone pickaxe.\")"
}
],
[
{
"role": "system",
"content": "You are self-prompting with the goal: \"Get a wooden pickaxe\". Respond:"
},
{
"role": "assistant",
"content": "What's around me? !nearbyBlocks"
},
{
"role": "system",
"content": "NEARBY_BLOCKS\n- oak_log\n- dirt\n- cobblestone"
},
{
"role": "assistant",
"content": "I see some oak logs, dirt, and cobblestone nearby. My goal is to craft a wooden pickaxe. I'll start by collecting some wood. !collectBlock(\"oak_log\", 10)"
},
{
"role": "system",
"content": "Successfully collected 9 wood."
},
{
"role": "assistant",
"content": "I collected 9 oak logs. I need a pickaxe first, which requires planks and sticks. !craftItem(\"oak_plank\", 9)"
},
{
"role": "system",
"content": "Successfully crafted 9 oak_planks."
},
{
"role": "assistant",
"content": "I have crafted 9 oak planks. Now I need sticks. !craftItem(\"stick\", 2)"
},
{
"role": "system",
"content": "Successfully crafted 2 sticks."
},
{
"role": "assistant",
"content": "I have crafted 2 sticks. Now I can craft a wooden pickaxe. !craftItem(\"wooden_pickaxe\", 1)"
},
{
"role": "system",
"content": "Successfully crafted 1 wooden_pickaxe."
},
{
"role": "assistant",
"content": "Looks like I have a wooden pickaxe now! Goal accomplished. !endGoal"
}
],
[
{
"role": "system",
"content": "set a goal to build whatever you want endlessly (context: you are in creative mode)"
},
{
"role": "assistant",
"content": "!goal(\"Build something creative, then move over, and build again. Use separate newActions. Repeat forever.\")"
}
],
[
{
"role": "system",
"content": "You are self-prompting with the goal: 'Build a house'. Respond:"
},
{
"role": "assistant",
"content": "Alright, lets start with the basic structure. !newAction(\"Build an 8x8 base for the house.\")"
}
]
]
}

View file

@ -4,7 +4,7 @@ import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { createMindServer } from './src/server/mind_server.js';
import { mainProxy } from './src/process/main_proxy.js';
import { readFileSync } from 'fs';
import {readFileSync} from "fs";
function parseArguments() {
return yargs(hideBin(process.argv))

6
randy.json Normal file
View file

@ -0,0 +1,6 @@
{
"name": "randy",
"model": "gpt-4o-mini"
}

View file

@ -14,6 +14,7 @@ export default
"base_profile": "./profiles/defaults/survival.json", // also see creative.json, god_mode.json
"profiles": ((process.env.PROFILES) && JSON.parse(process.env.PROFILES)) || [
"./andy.json",
"./jill.json"
// "./profiles/gpt.json",
// "./profiles/claude.json",
// "./profiles/gemini.json",

View file

@ -3,7 +3,7 @@ import { Coder } from './coder.js';
import { Prompter } from '../models/prompter.js';
import { initModes } from './modes.js';
import { initBot } from '../utils/mcdata.js';
import { containsCommand, commandExists, executeCommand, truncCommandMessage, isAction, blacklistCommands } from './commands/index.js';
import { containsCommand, commandExists, executeCommand, truncCommandMessage, isAction } from './commands/index.js';
import { ActionManager } from './action_manager.js';
import { NPCContoller } from './npc/controller.js';
import { MemoryBank } from './memory_bank.js';
@ -13,10 +13,13 @@ import { handleTranslation, handleEnglishTranslation } from '../utils/translator
import { addViewer } from './viewer.js';
import settings from '../../settings.js';
import { serverProxy } from './agent_proxy.js';
// import { TechTreeHarvestValidator } from '../../tasks/validation_functions/task_validator.js';
import {getPosition} from './library/world.js'
import { Task } from './tasks.js';
export class Agent {
async start(profile_fp, load_mem=false, init_message=null, count_id=0, task_path=null, task_id=null) {
this.last_sender = null;
this.count_id = count_id;
try {
@ -42,13 +45,12 @@ export class Agent {
this.memory_bank = new MemoryBank();
console.log('Initializing self prompter...');
this.self_prompter = new SelfPrompter(this);
convoManager.initAgent(this);
convoManager.initAgent(this);
console.log('Initializing examples...');
await this.prompter.initExamples();
console.log('Initializing task...');
this.task = new Task(this, task_path, task_id);
const blocked_actions = this.task.blocked_actions || [];
blacklistCommands(blocked_actions);
this.blocked_actions = this.task.blocked_actions || [];
serverProxy.connect(this);
@ -62,11 +64,42 @@ export class Agent {
save_data = this.history.load();
}
if (this.task) {
this.task.loadTask(task_path, task_id);
this.taskTimeout = this.task.timeout || 300;
this.taskStartTime = Date.now();
if (this.task.type === 'harvest' || this.task.type === 'techtree') {
// todo: this validator doesn't exist?
// this.validator = new TechTreeHarvestValidator(this.task, this.bot);
}
// this.validator = new TechTreeHarvestValidator(this.task, this.bot);
} else {
console.log('called without task')
this.task = null;
this.taskTimeout = null;
this.validator = null;
}
// handle blocked actions
if (this.task && "blocked_actions" in this.task) {
if ("agent_number" in this.task && this.task.agent_number > 1) {
this.blocked_actions = this.task.blocked_actions[this.name];
console.log(`Blocked actions for ${this.name}:`, this.blocked_actions);
} else {
this.blocked_actions = this.task.blocked_actions;
console.log(`Blocked actions:`, this.blocked_actions);
}
}
console.log("Is validated:", this.validator && this.validator.validate());
this.bot.on('login', () => {
console.log(this.name, 'logged in!');
serverProxy.login();
// Set skin for profile, requires Fabric Tailor. (https://modrinth.com/mod/fabrictailor)
if (this.prompter.profile.skin)
this.bot.chat(`/skin set URL ${this.prompter.profile.skin.model} ${this.prompter.profile.skin.path}`);
@ -77,6 +110,7 @@ export class Agent {
const spawnTimeout = setTimeout(() => {
process.exit(0);
}, 30000);
this.bot.once('spawn', async () => {
try {
clearTimeout(spawnTimeout);
@ -88,27 +122,126 @@ export class Agent {
console.log(`${this.name} spawned.`);
this.clearBotLogs();
if (this.task) {
this.bot.chat(`/clear ${this.name}`);
console.log(`Cleared ${this.name}'s inventory.`);
}
//wait for a bit so inventory is cleared
await new Promise((resolve) => setTimeout(resolve, 500));
console.log(this.task && "agent_number" in this.task && this.task.agent_number > 1);
if (this.task && "agent_number" in this.task && this.task.agent_number > 1) {
var initial_inventory = this.task.initial_inventory[this.name];
console.log("Initial inventory:", initial_inventory);
} else if (this.task) {
console.log("Initial inventory:", this.task.initial_inventory);
var initial_inventory = this.task.initial_inventory;
}
if (this.task && "initial_inventory" in this.task) {
console.log("Setting inventory...");
console.log("Inventory to set:", initial_inventory);
for (let key of Object.keys(initial_inventory)) {
console.log('Giving item:', key);
this.bot.chat(`/give ${this.name} ${key} ${initial_inventory[key]}`);
};
//wait for a bit so inventory is set
await new Promise((resolve) => setTimeout(resolve, 500));
console.log("Done giving inventory items.");
}
// Function to generate random numbers
function getRandomOffset(range) {
return Math.floor(Math.random() * (range * 2 + 1)) - range;
}
let human_player_name = null;
// Finding if there is a human player on the server
for (const playerName in this.bot.players) {
const player = this.bot.players[playerName];
if (!convoManager.isOtherAgent(player.username)) {
console.log('Found human player:', player.username);
human_player_name = player.username
break;
}
}
// If there are multiple human players, teleport to the first one
// teleport near a human player if found by default
if (this.task && "agent_number" in this.task) {
var agent_names = this.task.agent_names;
if (human_player_name) {
console.log(`Teleporting ${this.name} to human ${human_player_name}`)
this.bot.chat(`/tp ${this.name} ${human_player_name}`) // teleport on top of the human player
}
else {
this.bot.chat(`/tp ${this.name} ${agent_names[0]}`) // teleport on top of the first agent
}
await new Promise((resolve) => setTimeout(resolve, 200));
}
else if (this.task) {
if (human_player_name) {
console.log(`Teleporting ${this.name} to human ${human_player_name}`)
this.bot.chat(`/tp ${this.name} ${human_player_name}`) // teleport on top of the human player
}
await new Promise((resolve) => setTimeout(resolve, 200));
}
// now all bots are teleport on top of each other (which kinda looks ugly)
// Thus, we need to teleport them to random distances to make it look better
/*
Note : We don't want randomness for construction task as the reference point matters a lot.
Another reason for no randomness for construction task is because, often times the user would fly in the air,
then set a random block to dirt and teleport the bot to stand on that block for starting the construction,
This was done by MaxRobinson in one of the youtube videos.
*/
if (this.task && this.task.type !== 'construction') {
const pos = getPosition(this.bot);
const xOffset = getRandomOffset(5);
const zOffset = getRandomOffset(5);
this.bot.chat(`/tp ${this.name} ${Math.floor(pos.x + xOffset)} ${pos.y + 3} ${Math.floor(pos.z + zOffset)}`);
await new Promise((resolve) => setTimeout(resolve, 200));
}
this._setupEventHandlers(save_data, init_message);
this.startEvents();
// this.task.initBotTask();
if (!load_mem) {
this.task.initBotTask();
}
console.log("HERE IS THE LOGGED TASK")
console.log(this.task)
this.task.initBotTask();
await new Promise((resolve) => setTimeout(resolve, 10000));
this.checkAllPlayersPresent();
} catch (error) {
console.error('Error in spawn event:', error);
process.exit(0);
throw error; //rethrow here instead
}
});
} catch (error) {
// Ensure we're not losing error details
console.error('Agent start failed with error')
console.error(error.message);
console.error(error.stack);
throw error; // Re-throw with preserved details
console.error('Agent start failed with error:', {
message: error.message || 'No error message',
stack: error.stack || 'No stack trace',
error: error
});
process.exit(0);
}
}
@ -133,7 +266,7 @@ export class Agent {
console.log(this.name, 'received message from', username, ':', message);
if (convoManager.isOtherAgent(username)) {
console.warn('received whisper from other bot??')
console.warn('recieved whisper from other bot??')
}
else {
let translation = await handleEnglishTranslation(message);
@ -143,8 +276,6 @@ export class Agent {
console.error('Error handling message:', error);
}
}
this.respondFunc = respondFunc
this.bot.on('whisper', respondFunc);
if (settings.profiles.length === 1)
@ -158,10 +289,10 @@ export class Agent {
};
if (save_data?.self_prompt) {
if (init_message) {
this.history.add('system', init_message);
}
await this.self_prompter.handleLoad(save_data.self_prompt, save_data.self_prompting_state);
let prompt = save_data.self_prompt;
// add initial message to history
this.history.add('system', prompt);
await this.self_prompter.start(prompt);
}
if (save_data?.last_sender) {
this.last_sender = save_data.last_sender;
@ -170,7 +301,7 @@ export class Agent {
message: `You have restarted and this message is auto-generated. Continue the conversation with me.`,
start: true
};
convoManager.receiveFromBot(this.last_sender, msg_package);
convoManager.recieveFromBot(this.last_sender, msg_package);
}
}
else if (init_message) {
@ -181,6 +312,18 @@ export class Agent {
}
}
checkAllPlayersPresent() {
if (!this.task || !this.task.agent_names) {
return;
}
const missingPlayers = this.task.agent_names.filter(name => !this.bot.players[name]);
if (missingPlayers.length > 0) {
console.log(`Missing players/bots: ${missingPlayers.join(', ')}`);
this.cleanKill('Not all required players/bots are present in the world. Exiting.', 4);
}
}
requestInterrupt() {
this.bot.interrupt_code = true;
this.bot.collectBlock.cancelTask();
@ -195,7 +338,7 @@ export class Agent {
shutUp() {
this.shut_up = true;
if (this.self_prompter.isActive()) {
if (this.self_prompter.on) {
this.self_prompter.stop(false);
}
convoManager.endAllConversations();
@ -203,6 +346,9 @@ export class Agent {
async handleMessage(source, message, max_responses=null) {
await this.checkTaskDone();
// if (this.task && this.validator && this.validator.validate()) {
// this.killBots();
// }
if (!source || !message) {
console.warn('Received empty message from', source);
return false;
@ -237,6 +383,13 @@ export class Agent {
this.routeResponse(source, execute_res);
return true;
}
} else {
console.log('Self-prompting:', message);
// if self_prompt contains something that indicates the goal is complete, stop self-prompting
if (message.includes('goal complete')) {
this.self_prompter.stop();
process.exit(0);
}
}
if (from_other_bot)
@ -247,7 +400,7 @@ export class Agent {
console.log('received message from', source, ':', message);
const checkInterrupt = () => this.self_prompter.shouldInterrupt(self_prompt) || this.shut_up || convoManager.responseScheduledFor(source);
let behavior_log = this.bot.modes.flushBehaviorLog();
if (behavior_log.trim().length > 0) {
const MAX_LOG = 500;
@ -262,16 +415,18 @@ export class Agent {
await this.history.add(source, message);
this.history.save();
if (!self_prompt && this.self_prompter.isActive()) // message is from user during self-prompting
if (!self_prompt && this.self_prompter.on) // message is from user during self-prompting
max_responses = 1; // force only respond to this message, then let self-prompting take over
for (let i=0; i<max_responses; i++) {
if (checkInterrupt()) break;
let history = this.history.getHistory();
let res = await this.prompter.promptConvo(history);
console.log(`${this.name} full response to ${source}: ""${res}""`);
if (res.trim().length === 0) {
if (res.trim().length === 0) {
console.warn('no response')
break; // empty response ends loop
}
@ -325,7 +480,6 @@ export class Agent {
}
async routeResponse(to_player, message) {
if (this.shut_up) return;
let self_prompt = to_player === 'system' || to_player === this.name;
if (self_prompt && this.last_sender) {
// this is for when the agent is prompted by system while still in conversation
@ -367,8 +521,14 @@ export class Agent {
}
}
startEvents() {
async startEvents() {
// Custom events
// this.bot.on('spawn', () => {
// //check that inventory has been set
// });
this.bot.on('time', () => {
if (this.bot.time.timeOfDay == 0)
this.bot.emit('sunrise');
@ -420,6 +580,9 @@ export class Agent {
}
});
this.bot.on('idle', () => {
if (this.task && this.validator && this.validator.validate()) {
this.killBots();
}
this.bot.clearControlStates();
this.bot.pathfinder.stop(); // clear any lingering pathfinder
this.bot.modes.unPauseAll();
@ -445,12 +608,60 @@ export class Agent {
}, INTERVAL);
this.bot.emit('idle');
// Check for task completion
if (this.task.data) {
setInterval(() => {
let res = this.task.isDone();
if (res) {
// TODO kill other bots
this.cleanKill(res.message, res.code);
}
}, 1000);
}
}
async killBots() {
this.bot.chat('Task completed!');
this.bot.chat(`/clear @p`);
// Kick other bots
if (!this.task || !this.task.agent_number) {
await this.cleanKill('Task completed', 2);
return;
}
const agent_names = this.task.agent_names;
console.log('All agent names:', agent_names);
console.log('My name:', this.name);
const botNames = agent_names.filter(botName => botName !== this.name);
console.log('Kicking bots:', botNames);
botNames.forEach(botName => {
this.bot.chat(`/kick ${botName}`);
console.log(`/kick ${botName}`);
});
await this.cleanKill('Task completed, exiting', 2);
}
async update(delta) {
await this.bot.modes.update();
await this.self_prompter.update(delta);
try {
if (this.task && this.taskTimeout) {
const elapsedTime = (Date.now() - this.taskStartTime) / 1000;
if (elapsedTime >= this.taskTimeout) {
console.log('Task timeout reached. Task unsuccessful.');
await this.cleanKill('Task unsuccessful: Timeout reached', 3);
}
}
} catch (e) {
console.error("Caught an error while checking timeout reached",e);
}
this.self_prompter.update(delta);
await this.checkTaskDone();
// if (this.task.data) {
// let res = this.task.isDone();
// if (res) {
@ -466,13 +677,20 @@ export class Agent {
return !this.actions.executing && !this.coder.generating;
}
cleanKill(msg='Killing agent process...', code=1) {
this.history.add('system', msg);
if (code === 2 || code === 3 || code === 4) {
this.bot.chat('Exiting the world permanently.');
}
else {
this.bot.chat('Restarting.')
}
this.bot.chat(code > 1 ? 'Restarting.': 'Exiting.');
this.history.save();
process.exit(code);
}
async checkTaskDone() {
if (this.task.data) {
let res = this.task.isDone();

View file

@ -2,6 +2,7 @@ import * as skills from '../library/skills.js';
import settings from '../../../settings.js';
import convoManager from '../conversation.js';
function runAsAction (actionFn, resume = false, timeout = -1) {
let actionLabel = null; // Will be set on first use
@ -406,7 +407,7 @@ export const actionsList = [
convoManager.endConversation(player_name);
return `Converstaion with ${player_name} ended.`;
}
},
},
// { // commented for now, causes confusion with goal command
// name: '!npcGoal',
// description: 'Set a simple goal for an item or building to automatically work towards. Do not use for complex goals.',

View file

@ -2,6 +2,7 @@ import * as world from '../library/world.js';
import * as mc from '../../utils/mcdata.js';
import { getCommandDocs } from './index.js';
import convoManager from '../conversation.js';
import { checkLevelBlueprint, checkBlueprint } from '../construction_tasks.js';
const pad = (str) => {
return '\n' + str + '\n';
@ -177,6 +178,46 @@ export const queryList = [
perform: async function (agent) {
return "Saved place names: " + agent.memory_bank.getKeys();
}
},
{
name: '!checkBlueprintLevel',
description: 'Check if the level is complete and what blocks still need to be placed for the blueprint',
params: {
'levelNum': { type: 'int', description: 'The level number to check.', domain: [0, Number.MAX_SAFE_INTEGER] }
},
perform: function (agent, levelNum) {
let res = checkLevelBlueprint(agent, levelNum);
console.log(res);
return pad(res);
}
},
{
name: '!checkBlueprint',
description: 'Check what blocks still need to be placed for the blueprint',
perform: function (agent) {
let res = checkBlueprint(agent);
return pad(res);
}
},
{
name: '!getBlueprint',
description: 'Get the blueprint for the building',
perform: function (agent) {
let res = agent.task.blueprint.explain();
return pad(res);
}
},
{
name: '!getBlueprintLevel',
description: 'Get the blueprint for the building',
params: {
'levelNum': { type: 'int', description: 'The level number to check.', domain: [0, Number.MAX_SAFE_INTEGER] }
},
perform: function (agent, levelNum) {
let res = agent.task.blueprint.explainLevel(levelNum);
console.log(res);
return pad(res);
}
},
{
name: '!getCraftingPlan',

View file

@ -0,0 +1,947 @@
import {Vec3} from 'vec3';
export class ConstructionTaskValidator {
constructor(data, agent) {
this.blueprint = new Blueprint(data.blueprint);
this.agent = agent;
}
validate() {
try {
//todo: somehow make this more of a percentage or something
console.log('Validating task...');
let valid = false;
let score = 0;
let result = this.blueprint.check(this.agent.bot);
if (result.mismatches.length === 0) {
valid = true;
console.log('Task is complete');
}
let total_blocks = result.mismatches.length + result.matches.length;
score = (result.matches.length / total_blocks) * 100;
console.log(`Task is ${score}% complete`);
return valid;
} catch (error) {
console.error('Error validating task:', error);
return false;
}
}
}
export function resetConstructionWorld(bot, blueprint) {
console.log('Resetting world...');
const starting_position = blueprint.levels[0].coordinates;
const length = blueprint.levels[0].placement.length + 5;
const height = blueprint.levels.length + 5;
const width = blueprint.levels[0].placement[0].length + 5;
const command = `/fill ${starting_position[0]} ${starting_position[1]} ${starting_position[2]} ${starting_position[0] + width} ${starting_position[1] + height} ${starting_position[2] + length} air`;
bot.chat(command);
console.log('World reset');
}
export function checkLevelBlueprint(agent, levelNum) {
const blueprint = agent.task.blueprint;
const bot = agent.bot;
const result = blueprint.checkLevel(bot, levelNum);
if (result.mismatches.length === 0) {
return `Level ${levelNum} is correct`;
} else {
let explanation = blueprint.explainLevelDifference(bot, levelNum);
return explanation;
}
}
export function checkBlueprint(agent) {
console.log('Checking blueprint...');
console.log(agent);
const blueprint = agent.task.blueprint;
const bot = agent.bot;
const result = blueprint.check(bot);
if (result.mismatches.length === 0) {
return "Blueprint is correct";
} else {
let explanation = blueprint.explainBlueprintDifference(bot);
return explanation;
}
}
export class Blueprint {
constructor(blueprint) {
this.data = blueprint;
}
explain() {
var explanation = "";
// todo: we need to limit this to be a certain amount of levels to not overload memory...
for (let item of this.data.levels) {
var coordinates = item.coordinates;
explanation += `Level ${item.level}: `;
explanation += `Start at coordinates X: ${coordinates[0]}, Y: ${coordinates[1]}, Z: ${coordinates[2]}`;
// let placement_string = this._getPlacementString(item.placement);
// explanation += `\n${placement_string}\n`;
}
return explanation;
}
_getPlacementString(placement) {
var placement_string = "[\n";
for (let row of placement) {
placement_string += "[";
for (let i = 0; i < row.length - 1; i++) {
let item = row[i];
placement_string += `${item}, `;
}
let final_item = row[row.length - 1];
placement_string += `${final_item}],\n`;
}
placement_string += "]";
return placement_string;
}
explainLevel(levelNum) {
const levelData = this.data.levels[levelNum];
var explanation = `Level ${levelData.level} `;
explanation += `starting at coordinates X: ${levelData.coordinates[0]}, Y: ${levelData.coordinates[1]}, Z: ${levelData.coordinates[2]}`;
let placement_string = this._getPlacementString(levelData.placement);
explanation += `\n${placement_string}\n`;
return explanation;
}
explainBlueprintDifference(bot) {
var explanation = "";
const levels = this.data.levels;
for (let i = 0; i < levels.length; i++) {
let level_explanation = this.explainLevelDifference(bot, i);
explanation += level_explanation + "\n";
}
return explanation;
}
explainLevelDifference(bot, levelNum) {
const results = this.checkLevel(bot, levelNum);
const mismatches = results.mismatches;
const levelData = this.data.levels[levelNum];
if (mismatches.length === 0) {
return `Level ${levelData.level} is complete`;
}
var explanation = `Level ${levelData.level} `;
// explanation += `at coordinates X: ${levelData.coordinates[0]}, Y: ${levelData.coordinates[1]}, Z: ${levelData.coordinates[2]}`;
explanation += " requires the following fixes:\n";
for (let item of mismatches) {
if (item.actual === 'air') {
explanation += `Place ${item.expected} at coordinates X: ${item.coordinates[0]}, Y: ${item.coordinates[1]}, Z: ${item.coordinates[2]}\n`;
} else if (item.expected === 'air') {
explanation += `Remove the ${item.actual} at coordinates X: ${item.coordinates[0]}, Y: ${item.coordinates[1]}, Z: ${item.coordinates[2]}\n`;
} else {
explanation += `Replace the ${item.actual} with a ${item.expected} at coordinates X: ${item.coordinates[0]}, Y: ${item.coordinates[1]}, Z: ${item.coordinates[2]} \n`;
}
}
return explanation;
}
check(bot) {
if (!bot || typeof bot !== 'object' || !bot.hasOwnProperty('blockAt')) {
throw new Error('Invalid bot object. Expected a mineflayer bot.');
}
const levels = this.data.levels;
const mismatches = [];
const matches = [];
for (let i = 0; i < levels.length; i++) {
const result = this.checkLevel(bot, i);
mismatches.push(...result.mismatches);
matches.push(...result.matches);
}
return {
"mismatches": mismatches,
"matches": matches
};
}
checkLevel(bot, levelNum) {
const levelData = this.data.levels[levelNum];
const startCoords = levelData.coordinates;
const placement = levelData.placement;
const mismatches = [];
const matches = [];
for (let zOffset = 0; zOffset < placement.length; zOffset++) {
const row = placement[zOffset];
for (let xOffset = 0; xOffset < row.length; xOffset++) {
const blockName = row[xOffset];
const x = startCoords[0] + xOffset;
const y = startCoords[1];
const z = startCoords[2] + zOffset;
try {
const blockAtLocation = bot.blockAt(new Vec3(x, y, z));
const actualBlockName = blockAtLocation ? bot.registry.blocks[blockAtLocation.type].name : "air";
// Skip if both expected and actual block are air
if (blockName === "air" && actualBlockName === "air") {
continue;
}
if (actualBlockName !== blockName) {
mismatches.push({
level: levelData.level,
coordinates: [x, y, z],
expected: blockName,
actual: actualBlockName
});
} else {
matches.push({
level: levelData.level,
coordinates: [x, y, z],
expected: blockName,
actual: actualBlockName
});
}
} catch (err) {
console.error(`Error getting block at (${x}, ${y}, ${z}):`, err);
return false; // Stop checking if there's an issue getting blocks
}
}
}
return {
"mismatches": mismatches,
"matches": matches
};
}
/**
* Takes in the blueprint, and then converts it into a set of /setblock commands for the bot to follow
* @Returns: An object containing the setblock commands as a list of strings, and a position nearby the blueprint but not in it
* @param blueprint
*/
autoBuild() {
const commands = [];
let blueprint = this.data
let minX = Infinity, maxX = -Infinity;
let minY = Infinity, maxY = -Infinity;
let minZ = Infinity, maxZ = -Infinity;
for (const level of blueprint.levels) {
console.log(level.level)
const baseX = level.coordinates[0];
const baseY = level.coordinates[1];
const baseZ = level.coordinates[2];
const placement = level.placement;
// Update bounds
minX = Math.min(minX, baseX);
maxX = Math.max(maxX, baseX + placement[0].length - 1);
minY = Math.min(minY, baseY);
maxY = Math.max(maxY, baseY);
minZ = Math.min(minZ, baseZ);
maxZ = Math.max(maxZ, baseZ + placement.length - 1);
// Loop through the 2D placement array
for (let z = 0; z < placement.length; z++) {
for (let x = 0; x < placement[z].length; x++) {
const blockType = placement[z][x];
if (blockType) {
const setblockCommand = `/setblock ${baseX + x} ${baseY} ${baseZ + z} ${blockType}`;
commands.push(setblockCommand);
}
}
}
}
// Calculate a position nearby the blueprint but not in it
const nearbyPosition = {
x: maxX + 5, // Move 5 blocks to the right
y: minY, // Stay on the lowest level of the blueprint
z: minZ // Stay aligned with the front of the blueprint
};
return { commands, nearbyPosition };
}
/**
* Takes in a blueprint, and returns a set of commands to clear up the space.
*
*/
autoDelete() {
const commands = [];
let blueprint = this.data
let minX = Infinity, maxX = -Infinity;
let minY = Infinity, maxY = -Infinity;
let minZ = Infinity, maxZ = -Infinity;
for (const level of blueprint.levels) {
const baseX = level.coordinates[0];
const baseY = level.coordinates[1];
const baseZ = level.coordinates[2];
const placement = level.placement;
// Update bounds
minX = Math.min(minX, baseX);
maxX = Math.max(maxX, baseX + placement[0].length - 1);
minY = Math.min(minY, baseY);
maxY = Math.max(maxY, baseY);
minZ = Math.min(minZ, baseZ);
maxZ = Math.max(maxZ, baseZ + placement.length - 1);
// Loop through the 2D placement array
for (let z = 0; z < placement.length; z++) {
for (let x = 0; x < placement[z].length; x++) {
const blockType = placement[z][x];
if (blockType) {
const setblockCommand = `/setblock ${baseX + x} ${baseY} ${baseZ + z} air`;
commands.push(setblockCommand);
}
}
}
}
// Calculate a position nearby the blueprint but not in it
const nearbyPosition = {
x: maxX + 5, // Move 5 blocks to the right
y: minY, // Stay on the lowest level of the blueprint
z: minZ // Stay aligned with the front of the blueprint
};
return { commands, nearbyPosition };
}
}
/**
* Systematically builds the houses by placing them next to the already existing rooms. Still uses randomness for what gets placed next.
* @param m width of the 3D space
* @param n height of the 3D space
* @param p depth of the 3D space
* @param rooms Number of rooms to attempt to generate
* @param minRoomWidth
* @param minRoomLength
* @param minRoomDepth
* @param roomVariance How much the room size will vary
* @param wrapping material of wrapping (air, glass, etc...) -> default is air
* @param carpetStyle 0,1,2 increasingly more complex
* @param windowStyle 0,1,2 increasingly more complex
* @param complexity 0,1,2,3,4 for increasingly complex materials for room generation
* @returns a blueprint object
*/
export function proceduralGeneration(m = 20,
n = 20,
p = 20,
rooms = 8,
minRoomWidth = 5,
minRoomLength = 5,
minRoomDepth = 6,
roomVariance = 5,
wrapping = "air",
carpetStyle = 1,
windowStyle = 1,
complexity = 4) {
// Build 3D space
const matrix = Array.from({length: p}, () =>
Array.from({length: m}, () =>
Array(n).fill('air')
)
);
// set materials
let roomMaterials = ["stone", "terracotta", "quartz_block", "copper_block", "purpur_block"]
if (complexity < roomMaterials.length) {
roomMaterials = roomMaterials.slice(0, complexity + 1);
}
// Mark entire outer border with 'stone'
for (let z = 0; z < p; z++) {
for (let x = 0; x < m; x++) {
for (let y = 0; y < n; y++) {
if (
z === 0 || z === p - 1 || // Top and bottom faces
x === 0 || x === m - 1 || // Front and back faces
y === 0 || y === n - 1 // Left and right faces
) {
matrix[z][x][y] = 'stone';
}
}
}
}
// Replace outer layer with wrap
for (let z = 0; z < p; z++) {
for (let x = 0; x < m; x++) {
for (let y = 0; y < n; y++) {
if (
(z === p - 1 || // Top face
x === 0 || x === m - 1 || // Front and back faces
y === 0 || y === n - 1) // Left and right faces
) {
matrix[z][x][y] = wrapping;
}
}
}
}
let placedRooms = 0;
let lastRoom = null;
// Direction probabilities (e.g., 'above': 40%, 'left': 15%, etc.)
const directionChances = [
{direction: 'above', chance: 0.15},
{direction: 'left', chance: 0.15},
{direction: 'right', chance: 0.15},
{direction: 'forward', chance: 0.15},
{direction: 'backward', chance: 0.15},
];
// Function to pick a random direction based on percentages
function getRandomDirection() {
const rand = Math.random();
let cumulative = 0;
for (const {direction, chance} of directionChances) {
cumulative += chance;
if (rand <= cumulative) return direction;
}
return directionChances[1].direction; // Fallback to the first direction
}
// Ensures no rooms overlap except at edges
function isSpaceValid(newX, newY, newZ, newLength, newWidth, newDepth) {
for (let di = 0; di < newDepth; di++) {
for (let dj = 0; dj < newLength; dj++) {
for (let dk = 0; dk < newWidth; dk++) {
const x = newX + dj;
const y = newY + dk;
const z = newZ + di;
// Skip checking the outermost borders of the new room (these can overlap with stone)
if (dj === 0 || dj === newLength - 1 ||
dk === 0 || dk === newWidth - 1 ||
di === 0 || di === newDepth - 1) {
continue;
}
// For non-border spaces, ensure they're air
if (matrix[z][x][y] !== 'air') {
return false;
}
}
}
}
return true;
}
function validateAndBuildBorder(matrix, newX, newY, newZ, newLength, newWidth, newDepth, m, n, p, material) {
// Allow rooms to use the matrix edges (note the <= instead of <)
if (
newX >= 0 && newX + newLength <= m &&
newY >= 0 && newY + newWidth <= n &&
newZ >= 0 && newZ + newDepth <= p &&
isSpaceValid(newX, newY, newZ, newLength, newWidth, newDepth)
) {
console.log(`Placing room at (${newX}, ${newY}, ${newZ}) with dimensions (${newLength}x${newWidth}x${newDepth})`);
for (let di = 0; di < newDepth; di++) {
for (let dj = 0; dj < newLength; dj++) {
for (let dk = 0; dk < newWidth; dk++) {
const x = newX + dj;
const y = newY + dk;
const z = newZ + di;
// If this is at a matrix border, don't modify it
if (z === 0) {
continue;
}
// if (x === 0 || x === m - 1 ||
// y === 0 || y === n - 1 ||
// z === 0 || z === p - 1) {
// continue;
// }
// For non-border spaces, check if this is a floor that should be shared
//was: === 'stone'
if (di === 0 && matrix[z - 1][x][y] !== 'air') {
// Skip creating floor if there's a ceiling below
matrix[z][x][y] = 'air';
} else if (di === 0 || di === newDepth - 1 ||
dj === 0 || dj === newLength - 1 ||
dk === 0 || dk === newWidth - 1) {
matrix[z][x][y] = material;
} else {
matrix[z][x][y] = 'air';
}
}
}
}
return true;
}
return false;
}
function addDoor(matrix, x, y, z, material) {
matrix[z][x][y] = material;
// Place the lower half of the door
matrix[z + 1][x][y] = 'dark_oak_door[half=lower, hinge=left]';
// Place the upper half of the door
matrix[z + 2][x][y] = 'dark_oak_door[half=upper, hinge=left]';
}
// Takes in a room and randomly converts some faces to be windows
function addWindowsAsSquares(matrix, x, y, z, newLength, newWidth, newDepth, material) {
// Matrix dimensions
const matrixDepth = matrix.length;
const matrixLength = matrix[0].length;
const matrixWidth = matrix[0][0].length;
const windowX = Math.ceil(minRoomWidth / 2)
const windowY = Math.ceil(minRoomLength / 2)
const windowZ = Math.ceil(minRoomDepth / 2)
// Helper function to check if coordinates are within bounds
function isInBounds(z, x, y) {
return z >= 0 && z < matrixDepth &&
x >= 0 && x < matrixLength &&
y >= 0 && y < matrixWidth;
}
// Front and back faces (z is constant)
if (Math.random() < 0.8) {
let centerX = x + Math.floor(newLength / 2 - windowX / 2);
let centerY = y + Math.floor(newWidth / 2 - windowY / 2);
for (let dx = 0; dx <= windowX; dx++) {
for (let dy = 0; dy <= windowY; dy++) {
let frontZ = z;
let backZ = z + newDepth - 1;
if (isInBounds(frontZ, centerX + dx, centerY + dy) &&
matrix[frontZ][centerX + dx][centerY + dy] === material) {
matrix[frontZ][centerX + dx][centerY + dy] = 'glass';
}
if (isInBounds(backZ, centerX + dx, centerY + dy) &&
matrix[backZ][centerX + dx][centerY + dy] === material) {
matrix[backZ][centerX + dx][centerY + dy] = 'glass';
}
}
}
}
// Left and right faces (x is constant)
if (Math.random() < 0.8) {
let centerZ = z + Math.floor(newDepth / 2 - windowZ / 2);
let centerY = y + Math.floor(newWidth / 2 - windowY / 2);
for (let dz = 0; dz <= windowZ; dz++) {
for (let dy = 0; dy <= windowY; dy++) {
let leftX = x;
let rightX = x + newLength - 1;
if (isInBounds(centerZ + dz, leftX, centerY + dy) &&
matrix[centerZ + dz][leftX][centerY + dy] === material) {
matrix[centerZ + dz][leftX][centerY + dy] = 'glass';
}
if (isInBounds(centerZ + dz, rightX, centerY + dy) &&
matrix[centerZ + dz][rightX][centerY + dy] === material) {
matrix[centerZ + dz][rightX][centerY + dy] = 'glass';
}
}
}
}
// Top and bottom faces (y is constant)
if (Math.random() < 0.8) {
let centerX = x + Math.floor(newLength / 2 - windowX / 2);
let centerZ = z + Math.floor(newDepth / 2 - windowZ / 2);
for (let dx = 0; dx <= windowX; dx++) {
for (let dz = 0; dz <= windowZ; dz++) {
let bottomY = y;
let topY = y + newWidth - 1;
if (isInBounds(centerZ + dz, centerX + dx, bottomY) &&
matrix[centerZ + dz][centerX + dx][bottomY] === material) {
matrix[centerZ + dz][centerX + dx][bottomY] = 'glass';
}
if (isInBounds(centerZ + dz, centerX + dx, topY) &&
matrix[centerZ + dz][centerX + dx][topY] === material) {
matrix[centerZ + dz][centerX + dx][topY] = 'glass';
}
}
}
}
}
function addWindowsAsPlane(matrix, x, y, z, newLength, newWidth, newDepth, material) {
// Ensure the new dimensions are within bounds
const maxX = matrix[0].length;
const maxY = matrix[0][0].length;
const maxZ = matrix.length;
// Each face has a 30% chance of becoming a window
if (Math.random() < 0.8) {
for (let dx = 0; dx < newLength; dx++) {
for (let dy = 0; dy < newWidth; dy++) {
let frontZ = z;
let backZ = z + newDepth - 1;
// Check bounds before modifying the matrix
if (frontZ >= 0 && frontZ < maxZ && x + dx >= 0 && x + dx < maxX && y + dy >= 0 && y + dy < maxY) {
if (matrix[frontZ][x + dx][y + dy] === material) {
matrix[frontZ][x + dx][y + dy] = 'glass';
}
}
if (backZ >= 0 && backZ < maxZ && x + dx >= 0 && x + dx < maxX && y + dy >= 0 && y + dy < maxY) {
if (matrix[backZ][x + dx][y + dy] === material) {
matrix[backZ][x + dx][y + dy] = 'glass';
}
}
}
}
}
if (Math.random() < 0.8) {
for (let dz = 0; dz < newDepth; dz++) {
for (let dy = 0; dy < newWidth; dy++) {
let leftX = x;
let rightX = x + newLength - 1;
// Check bounds before modifying the matrix
if (leftX >= 0 && leftX < maxX && z + dz >= 0 && z + dz < maxZ && y + dy >= 0 && y + dy < maxY) {
if (matrix[z + dz][leftX][y + dy] === material) {
matrix[z + dz][leftX][y + dy] = 'glass';
}
}
if (rightX >= 0 && rightX < maxX && z + dz >= 0 && z + dz < maxZ && y + dy >= 0 && y + dy < maxY) {
if (matrix[z + dz][rightX][y + dy] === material) {
matrix[z + dz][rightX][y + dy] = 'glass';
}
}
}
}
}
}
// out of commission
function addStairs(matrix, x, y, z, direction) {
let dz = 0; // Change in Z direction
let dx = 0; // Change in X direction
let facing = '';
// Determine direction and facing
switch (direction) {
case 'north':
dz = -1;
facing = 'oak_stairs[facing=north]';
break;
case 'south':
dz = 1;
facing = 'oak_stairs[facing=south]';
break;
case 'east':
dx = 1;
facing = 'oak_stairs[facing=east]';
break;
case 'west':
dx = -1;
facing = 'oak_stairs[facing=west]';
break;
default:
console.error('Invalid stair direction');
return;
}
// Bore stair pattern downwards until we hit a floor or the matrix edge
let currentZ = z;
while (currentZ > 0 && matrix[currentZ - 1][x][y] === 'air') {
// Place stone as foundation
matrix[currentZ - 1][x][y] = 'stone';
// Place stair above the stone
matrix[currentZ][x][y] = facing;
// Move down diagonally
x += dx;
y += dz;
currentZ--;
// Check if we've hit the edge
if (x < 0 || x >= matrix[0].length || y < 0 || y >= matrix[0][0].length) break;
}
}
function addCarpet(probability, matrix, newX, newY, newZ, newLength, newWidth, material) {
let colors = ["blue", "cyan", "light_blue", "lime"];
// Iterate through the dimensions of the room
for (let dx = 1; dx < newLength - 1; dx++) {
for (let dy = 1; dy < newWidth - 1; dy++) {
let x = newX + dx;
let y = newY + dy;
let z = newZ; // Start at floor level
// Check if there is floor (not air)
if (matrix[z][x][y] === material) {
// Consider a random probability of adding a carpet
if (Math.random() < probability) {
// Choose a random color for the carpet
let randomColor = colors[Math.floor(Math.random() * colors.length)];
// Add carpet one z position above the floor with a random color
matrix[z + 1][x][y] = `${randomColor}_carpet`;
}
}
}
}
}
function addLadder(matrix, x, y, z) {
let currentZ = z + 1;
// turn the floor into air where person would go up
matrix[currentZ][x + 1][y] = 'air';
// Build the first 3 ladder segments from floor level downwards
for (let i = 0; i < 3; i++) {
matrix[currentZ][x][y] = 'ladder[facing=north]';
currentZ -= 1
}
// Continue building ladder downwards until a floor is hit or we reach the bottom
while (currentZ >= 0 && matrix[currentZ][x][y] === 'air') {
// Place ladder
matrix[currentZ][x][y] = 'ladder[facing=north]';
// Move down
currentZ--;
}
}
function embellishments(carpet, windowStyle, matrix, newX, newY, newZ, newLength, newWidth, newDepth, material) {
switch (windowStyle) {
case 0:
break;
case 1:
addWindowsAsSquares(matrix, newZ, newY, newZ, newLength, newWidth, newDepth, material)
break;
case 2:
addWindowsAsPlane(matrix, newZ, newY, newZ, newLength, newWidth, newDepth, material)
}
switch (carpet) {
case 0:
break;
case 1:
addCarpet(0.3, matrix, newX, newY, newZ, newLength, newWidth, material);
break;
case 2:
addCarpet(0.7, matrix, newX, newY, newZ, newLength, newWidth, material)
break;
}
}
// Places rooms until we can't, or we place all
// attempts random configurations of rooms in random directions.
while (placedRooms < rooms) {
let roomPlaced = false;
for (let attempt = 0; attempt < 150; attempt++) {
const material = roomMaterials[Math.floor(Math.random() * roomMaterials.length)];
// dimensions of room
const newLength = Math.max(minRoomLength, Math.floor(Math.random() * roomVariance) + minRoomLength);
const newWidth = Math.max(minRoomWidth, Math.floor(Math.random() * roomVariance) + minRoomWidth);
const newDepth = Math.max(minRoomDepth, Math.floor(Math.random() * Math.floor(roomVariance / 2)) + minRoomDepth);
let newX, newY, newZ;
// first room is special
if (placedRooms === 0) {
// First room placement
newX = Math.floor(Math.random() * (m - newLength - 1)) + 1;
newY = Math.floor(Math.random() * (n - newWidth - 1)) + 1;
newZ = 0; // Ground floor
if (validateAndBuildBorder(matrix, newX, newY, newZ, newLength, newWidth, newDepth, m, n, p, material)) {
lastRoom = {x: newX, y: newY, z: newZ, length: newLength, width: newWidth, depth: newDepth};
roomPlaced = true;
placedRooms++;
// Add doors to all four sides
// Left side
addDoor(matrix, newX, newY + Math.floor(newWidth / 2), newZ, material);
// Right side
addDoor(matrix, newX + newLength - 1, newY + Math.floor(newWidth / 2), newZ, material);
// Front side
addDoor(matrix, newX + Math.floor(newLength / 2), newY, newZ, material);
// Back side
addDoor(matrix, newX + Math.floor(newLength / 2), newY + newWidth - 1, newZ, material);
addCarpet(0.7, matrix, newX, newY, newZ, newLength, newWidth)
}
break;
} else {
const direction = getRandomDirection();
switch (direction) {
case 'above':
newX = lastRoom.x;
newY = lastRoom.y;
newZ = lastRoom.z + lastRoom.depth - 1;
if (validateAndBuildBorder(matrix, newX, newY, newZ, newLength, newWidth, newDepth, m, n, p, material)) {
embellishments(carpetStyle, windowStyle, matrix, newX, newY, newZ, newLength, newWidth, newDepth, material)
addLadder(matrix, lastRoom.x + Math.floor(lastRoom.length / 2),
lastRoom.y + Math.floor(lastRoom.width / 2),
newZ); // Adding the ladder
lastRoom = {x: newX, y: newY, z: newZ, length: newLength, width: newWidth, depth: newDepth};
roomPlaced = true;
placedRooms++;
break;
}
break;
case 'left':
newX = lastRoom.x - newLength + 1;
newY = lastRoom.y;
newZ = lastRoom.z;
if (validateAndBuildBorder(matrix, newX, newY, newZ, newLength, newWidth, newDepth, m, n, p, material)) {
embellishments(carpetStyle, windowStyle, matrix, newX, newY, newZ, newLength, newWidth, newDepth, material)
addDoor(matrix, lastRoom.x, lastRoom.y + Math.floor(lastRoom.width / 2), lastRoom.z, material);
lastRoom = {x: newX, y: newY, z: newZ, length: newLength, width: newWidth, depth: newDepth};
roomPlaced = true;
placedRooms++;
break;
}
break;
case 'right':
newX = lastRoom.x + lastRoom.length - 1;
newY = lastRoom.y;
newZ = lastRoom.z;
if (validateAndBuildBorder(matrix, newX, newY, newZ, newLength, newWidth, newDepth, m, n, p, material)) {
embellishments(carpetStyle, windowStyle, matrix, newX, newY, newZ, newLength, newWidth, newDepth, material)
addDoor(matrix, lastRoom.x + lastRoom.length - 1,
lastRoom.y + Math.floor(lastRoom.width / 2),
lastRoom.z, material);
lastRoom = {x: newX, y: newY, z: newZ, length: newLength, width: newWidth, depth: newDepth};
roomPlaced = true;
placedRooms++;
break;
}
break;
case 'forward':
newX = lastRoom.x;
newY = lastRoom.y + lastRoom.width - 1;
newZ = lastRoom.z;
if (validateAndBuildBorder(matrix, newX, newY, newZ, newLength, newWidth, newDepth, m, n, p, material)) {
embellishments(carpetStyle, windowStyle, matrix, newX, newY, newZ, newLength, newWidth, newDepth, material)
addDoor(matrix, lastRoom.x + Math.floor(lastRoom.length / 2),
lastRoom.y + lastRoom.width - 1,
lastRoom.z, material);
lastRoom = {x: newX, y: newY, z: newZ, length: newLength, width: newWidth, depth: newDepth};
roomPlaced = true;
placedRooms++;
break;
}
break;
case 'backward':
newX = lastRoom.x;
newY = lastRoom.y - newWidth + 1;
newZ = lastRoom.z;
if (validateAndBuildBorder(matrix, newX, newY, newZ, newLength, newWidth, newDepth, m, n, p, material)) {
embellishments(carpetStyle, windowStyle, matrix, newX, newY, newZ, newLength, newWidth, newDepth, material)
addDoor(matrix, lastRoom.x + Math.floor(lastRoom.length / 2),
lastRoom.y,
lastRoom.z, material);
lastRoom = {x: newX, y: newY, z: newZ, length: newLength, width: newWidth, depth: newDepth};
roomPlaced = true;
placedRooms++;
break;
}
break;
}
if (roomPlaced) {
break;
}
}
}
if (!roomPlaced) {
console.warn(`Could not place room ${placedRooms + 1}`);
break;
}
}
return matrixToBlueprint(matrix, [148,-60,-170])
}
/**
* Converts a 3D matrix into a Minecraft blueprint format
* @param {Array<Array<Array<string>>>} matrix - 3D matrix of block types
* @param {number[]} startCoord - Starting coordinates [x, y, z]
* @returns {Object} a Blueprint object in Minecraft format
*/
function matrixToBlueprint(matrix, startCoord) {
// Validate inputs
if (!Array.isArray(matrix) || !Array.isArray(startCoord) || startCoord.length !== 3) {
console.log(matrix)
throw new Error('Invalid input format');
}
const [startX, startY, startZ] = startCoord;
// CONSIDER: using blueprint class here?
return {
levels: matrix.map((level, levelIndex) => ({
level: levelIndex,
coordinates: [
startX,
startY + levelIndex,
startZ
],
placement: level.map(row =>
// Ensure each block is a string, default to 'air' if undefined
row.map(block => block?.toString() || 'air')
)
}))
};
}

View file

@ -3,6 +3,9 @@ import { executeCommand } from './commands/index.js';
import { getPosition } from './library/world.js';
import settings from '../../settings.js';
import { CookingTaskInitiator } from './task_types/cooking_tasks.js';
import { Vec3 } from 'vec3';
import { ConstructionTaskValidator, Blueprint } from './construction_tasks.js';
import {autoBuild, autoDelete} from "../../test/test_blueprint_layout.js";
/**
* Validates the presence of required items in an agent's inventory
@ -13,8 +16,8 @@ import { CookingTaskInitiator } from './task_types/cooking_tasks.js';
function checkItemPresence(data, agent) {
// Helper function to check if target is a dictionary with quantities
function isTargetDictionaryWithQuantities(target) {
return typeof target === 'object' &&
!Array.isArray(target) &&
return typeof target === 'object' &&
!Array.isArray(target) &&
target !== null &&
Object.values(target).every(value => typeof value === 'number');
}
@ -61,10 +64,10 @@ function checkItemPresence(data, agent) {
try {
// First normalize targets to always have a consistent format
const targets = normalizeTargets(data.target);
// Determine the required quantities
const requiredQuantities = isTargetDictionaryWithQuantities(data.target)
? data.target
const requiredQuantities = isTargetDictionaryWithQuantities(data.target)
? data.target
: normalizeQuantities(targets, data.number_of_target);
// Count items in inventory
@ -83,7 +86,7 @@ function checkItemPresence(data, agent) {
for (const [item, requiredCount] of Object.entries(requiredQuantities)) {
const itemName = item.toLowerCase();
const currentCount = inventoryCount[itemName] || 0;
if (currentCount < requiredCount) {
allTargetsMet = false;
missingItems.push({
@ -117,16 +120,27 @@ export class Task {
this.taskTimeout = 300;
this.taskStartTime = Date.now();
this.validator = null;
this.reset_function = null;
this.blocked_actions = [];
this.task_id = task_id;
if (task_path && task_id) {
this.data = this.loadTask(task_path, task_id);
this.data = this.loadTask(task_path, task_id); //todo: does this even return anything?
this.task_type = this.data.type;
if (this.task_type === 'construction' && this.data.blueprint) {
this.blueprint = new Blueprint(this.data.blueprint);
this.goal = this.data.goal + ' \n' + this.blueprint.explain() + " \n" + "make sure to place the lower levels of the blueprint first";
this.conversation = this.data.conversation + ' \n' + this.blueprint.explain();
} else {
this.goal = this.data.goal;
this.conversation = this.data.conversation;
}
this.taskTimeout = this.data.timeout || 300;
this.taskStartTime = Date.now();
this.task_type = this.data.type;
// Set validator based on task_type
if (this.task_type === 'cooking' || this.task_type === 'techtree') {
if (this.task_type === 'construction') {
this.validator = new ConstructionTaskValidator(this.data, this.agent);
} else if (this.task_type === 'cooking' || this.task_type === 'techtree') {
this.validator = () => {
const result = checkItemPresence(this.data, this.agent);
return result.success;
@ -143,10 +157,11 @@ export class Task {
this.restrict_to_inventory = !!this.data.restrict_to_inventory;
if (this.data.goal)
this.blocked_actions.push('!endGoal');
if (this.data.conversation)
if (this.conversation)
this.blocked_actions.push('!endConversation');
console.log('Task loaded:', this.data);
}
this.name = this.agent.name;
this.available_agents = settings.profiles.map((p) => JSON.parse(readFileSync(p, 'utf8')).name);
}
@ -180,7 +195,8 @@ export class Task {
try {
const tasksFile = readFileSync(task_path, 'utf8');
const tasks = JSON.parse(tasksFile);
const task = tasks[task_id];
let task = tasks[task_id];
console.log('Loaded task:', task);
if (!task) {
throw new Error(`Task ${task_id} not found`);
}
@ -206,7 +222,7 @@ export class Task {
console.log('No other agents found. Task unsuccessful.');
return {"message": 'No other agents found', "code": 3};
}
if (this.taskTimeout) {
if (elapsedTime >= this.taskTimeout) {
console.log('Task timeout reached. Task unsuccessful.');
@ -216,7 +232,9 @@ export class Task {
return false;
}
async initBotTask() {
initBotTask = async () => {
await this.agent.bot.chat(`/clear ${this.name}`);
console.log(`Cleared ${this.name}'s inventory.`);
@ -225,7 +243,7 @@ export class Task {
if (this.data === null)
return;
if (this.task_type === 'cooking') {
this.initiator = new CookingTaskInitiator(this.data, this.agent);
} else {
@ -240,7 +258,7 @@ export class Task {
if (this.data.initial_inventory) {
console.log("Setting inventory...");
let initialInventory = {};
// Handle multi-agent inventory assignment
if (this.data.agent_count > 1) {
initialInventory = this.data.initial_inventory[this.agent.count_id.toString()] || {};
@ -306,7 +324,7 @@ export class Task {
let human_player_name = null;
let bot = this.agent.bot;
// Finding if there is a human player on the server
for (const playerName in bot.players) {
const player = bot.players[playerName];
@ -321,9 +339,20 @@ export class Task {
console.log(`Teleporting ${this.name} to human ${human_player_name}`)
bot.chat(`/tp ${this.name} ${human_player_name}`)
}
await new Promise((resolve) => setTimeout(resolve, 200));
// now all bots are teleport on top of each other (which kinda looks ugly)
// Thus, we need to teleport them to random distances to make it look better
/*
Note : We don't want randomness for construction task as the reference point matters a lot.
Another reason for no randomness for construction task is because, often times the user would fly in the air,
then set a random block to dirt and teleport the bot to stand on that block for starting the construction,
This was done by MaxRobinson in one of the youtube videos.
*/
if (this.data.type !== 'construction') {
const pos = getPosition(bot);
const xOffset = getRandomOffset(5);
@ -331,5 +360,84 @@ export class Task {
bot.chat(`/tp ${this.name} ${Math.floor(pos.x + xOffset)} ${pos.y + 3} ${Math.floor(pos.z + zOffset)}`);
await new Promise((resolve) => setTimeout(resolve, 200));
}
if (this.data.agent_count && this.data.agent_count > 1) {
// TODO wait for other bots to join
await new Promise((resolve) => setTimeout(resolve, 10000));
if (available_agents.length < this.data.agent_count) {
console.log(`Missing ${this.data.agent_count - available_agents.length} bot(s).`);
this.agent.killAll();
}
}
if (this.goal) {
console.log('Setting goal:', this.goal);
await executeCommand(this.agent, `!goal("${this.goal}")`);
}
if (this.conversation && this.agent.count_id === 0) {
let other_name = available_agents.filter(n => n !== name)[0];
await executeCommand(this.agent, `!startConversation("${other_name}", "${this.conversation}")`);
}
if (this.data.type === 'construction'){
//Ensures construction is cleaned out first. -> relies on cheats which are turned off?
if (this.blueprint){
const result = this.blueprint.autoDelete();
// const result = clearHouse(blueprint)
const commands = result.commands;
const nearbyPosition = result.nearbyPosition;
for (const command of commands) {
bot.chat(command);
}
}
else{
console.log('no construction blueprint?')
}
}
}
}
export function giveBlueprint(agent, blueprint) {
let bot = agent.bot;
let name = agent.name;
let blueprint_name = blueprint.name;
let blueprint_count = blueprint.count;
bot.chat(`/clear ${name}`);
console.log(`Cleared ${name}'s inventory.`);
bot.chat(`/give ${name} ${blueprint_name} ${blueprint_count}`);
console.log(`Gave ${name} ${blueprint_count} ${blueprint_name}(s).`);
}
/**
* Auto-builds blueprint in minecraft world
* @param agent
* @param blueprint must be of the blueprint class
*/
export function buildBlueprint(agent, blueprint){
let bot = agent.bot
const result = blueprint.autoBuild();
// const result = clearHouse(blueprint)
const commands = result.commands;
const nearbyPosition = result.nearbyPosition;
for (const command of commands) {
bot.chat(command);
}
}
/**
* auto-deletes a built blueprint
* @param agent
* @param blueprint must be of the blueprint class
*/
export function deleteBlueprint(agent, blueprint){
let bot = agent.bot
const result = blueprint.autoDelete()
const commands = result.commands;
const nearbyPosition = result.nearbyPosition;
for (const command of commands) {
bot.chat(command);
}
}

View file

@ -0,0 +1,25 @@
export class BlueprintGenerator {
constructor(num_levels,
num_rooms,
room_height,
materials) {
this.blueprint = {
"name": "Blueprint",
"nodes": [],
"connections": []
};
}
addNode(node) {
this.blueprint.nodes.push(node);
}
addConnection(connection) {
this.blueprint.connections.push(connection);
}
getBlueprint() {
return this.blueprint;
}
}

BIN
test/.DS_Store vendored Normal file

Binary file not shown.

View file

@ -0,0 +1,32 @@
import fs from 'fs';
// Read and parse the JSON file
const tasks = JSON.parse(fs.readFileSync('./example_multiagent_construction_tasks.json'));
// Validate format and count variants
const counts = {};
const expectedKeys = ['type', 'goal', 'conversation', 'agent_count', 'blueprint', 'initial_inventory'];
Object.keys(tasks).forEach(taskName => {
const task = tasks[taskName];
// Validate task format
if (!expectedKeys.every(key => key in task)) {
console.error(`Invalid task format in ${taskName}`);
return;
}
const category = taskName.split('_variant_')[0];
counts[category] = (counts[category] || 0) + 1;
});
console.log('\nVariants per category:');
Object.entries(counts).forEach(([category, count]) => {
console.log(`${category}: ${count}`);
});
console.log(`\nTotal tasks: ${Object.keys(tasks).length}`);
console.log(`Total categories: ${Object.keys(counts).length}`);
// const expectedTotal = 5 * 3 * 3* 3
// * 5; // materialLevels * roomCounts * windowStyles * carpetStyles * variants
// console.log(`Expected total tasks: ${expectedTotal}`);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,126 @@
import fs from 'fs';
import {proceduralGeneration} from "../../src/agent/construction_tasks.js";
function createInitialInventory(blueprint, agents) {
/*
params:
- blueprint object
- number of agents (for inventory initialization)
logic of the function:
- loop matrix
- every time a new material is hit, put it in a different agents inventory
-
*/
const inventories = {};
const materialCounts = {};
let currentAgent = 0;
// Initialize inventories
for (let i = 0; i < agents; i++) {
inventories[i] = {};
}
// Count materials in blueprint
for (const level of blueprint.levels) {
for (const row of level.placement) {
for (const block of row) {
if (block !== 'air') {
// Check if material contains 'door' or 'ladder' and convert appropriately
let materialKey = block;
if (block.includes('door')) {
materialKey = 'oak_door';
} else if (block.includes('ladder')) {
materialKey = 'ladder';
}
materialCounts[materialKey] = (materialCounts[materialKey] || 0) + 1;
}
}
}
}
// Distribute materials among agents
for (const [material, count] of Object.entries(materialCounts)) {
inventories[currentAgent][material] = count;
currentAgent = (currentAgent + 1) % agents;
}
// Todo: add tools to all agents (diamond pickaxe)
return inventories;
}
function calculateSpaceNeeded(rooms) {
const baseSize = 10;
const scaleFactor = Math.floor(rooms / 4) * 5;
return baseSize + scaleFactor;
}
function generateConstructionTasks() {
const tasks = {};
const variants = 1
const materialLevels = 5;
const roomCounts = [4, 6, 8];
const windowStyles = [0, 1, 2];
const carpetStyles = [0, 1, 2];
const timeout = 600 // 10 min base?
for (let m = 0; m < materialLevels; m++) {
for (let r = 0; r < roomCounts.length; r++) {
for (let w = 0; w < windowStyles.length; w++) {
for (let c = 0; c < carpetStyles.length; c++) {
for (let variant = 0; variant < variants; variant++) {
const rooms = roomCounts[r];
const spaceSize = calculateSpaceNeeded(rooms);
const blueprint = proceduralGeneration(
spaceSize,
spaceSize,
spaceSize,
rooms,
4,
4,
4,
5,
"air",
carpetStyles[c],
windowStyles[w],
m + 1
);
const taskName = `materials_${m}_rooms_${r}_window_${w}_carpet_${c}_variant_${variant}`;
tasks[taskName] = {
type: "construction",
goal: "Make a house with the blueprint",
conversation: "Let's share materials and make a house with the blueprint",
agent_count: 2,
initial_inventory: createInitialInventory(blueprint, 2),
timeout: timeout+(300*r), // 5 minute per additional level of complexity
blueprint: blueprint, //todo: make a pointer?
};
}
}
}
}
}
return tasks;
}
const tasks = generateConstructionTasks();
// Clear existing file content
fs.writeFileSync('./example_multiagent_construction_tasks.json', '');
// re-add
fs.writeFileSync(
'./example_multiagent_construction_tasks.json',
JSON.stringify(tasks, null, 2)
);
console.log("Generated tasks saved to example_multiagent_construction_tasks.json");

View file

@ -0,0 +1,69 @@
import mineflayer from 'mineflayer';
import { Vec3 } from 'vec3';
import { ConstructionTaskValidator, Blueprint, checkBlueprint, checkLevelBlueprint } from '../src/agent/tasks.js';
import { Agent } from '../src/agent/agent.js';
try {
const agent = new Agent("../andy.json",
false,
null,
0,
"../example_tasks.json",
"construction_house");
await new Promise((resolve) => setTimeout(resolve, 10000));
let result = await checkBlueprint(agent);
// console.log(result);
// const levelResult = await checkLevelBlueprint(agent, 0);
// console.log(levelResult);
} catch (error) {
console.error('An error occurred:', error);
process.exit(1);
}
// const validator = new ConstructionTaskValidator();
const blueprintData = {
"materials": {
"oak_planks": 20,
"oak_door": 1,
"stone": 26,
},
"levels": [
{
"level": 0,
"coordinates": [142, -60, -179],
"placement":
[
["stone", "stone", "oak_door", "stone", "stone"],
["stone", "air", "air", "air", "stone"],
["stone", "air", "air", "air", "stone"],
["stone", "stone", "stone", "stone", "stone"]
]
},
{
"level": 1,
"coordinates": [142, -59, -179],
"placement":
[
["stone", "stone", "oak_door", "stone", "stone"],
["stone", "air", "air", "air", "stone"],
["stone", "air", "air", "air", "stone"],
["stone", "stone", "stone", "stone", "stone"]
]
},
{
"level": 2,
"coordinates": [142, -58, -179],
"placement":
[
["oak_planks", "oak_planks", "oak_planks", "oak_planks", "oak_planks"],
["oak_planks", "oak_planks", "oak_planks", "oak_planks", "oak_planks"],
["oak_planks", "oak_planks", "oak_planks", "oak_planks", "oak_planks"],
["oak_planks", "oak_planks", "oak_planks", "oak_planks", "oak_planks"]
]
}
]
};

22
test/test_blueprint.js Normal file
View file

@ -0,0 +1,22 @@
import { Blueprint } from '../src/agent/tasks.js';
import { readFileSync } from 'fs';
//load file from example_tasks.json
const object = JSON.parse(readFileSync('example_tasks.json', 'utf8'));
console.log(object.construction_house.blueprint);
const blueprint = new Blueprint(object.construction_house.blueprint);
const placement = object.construction_house.blueprint.levels[0].placement;
console.log(placement);
var placement_string = "[\n";
for (let row of placement) {
placement_string += "[";
for (let i = 0; i < row.length - 1; i++) {
let item = row[i];
placement_string += `${item}, `;
}
let final_item = row[row.length - 1];
placement_string += `${final_item}],\n`;
}
placement_string += "]";
console.log(placement_string);
console.log(blueprint.explain());

View file

@ -0,0 +1,165 @@
import mineflayer from "mineflayer";
const bot = mineflayer.createBot({
host: 'localhost', // Replace with your server IP or hostname
port: 55916, // Replace with your server port
username: 'andy', // Replace with your bot's username
// password: 'your_bot_password' // Only if the server has online-mode=true
});
bot.on('spawn', async () => {
// generate a blueprint
const blueprint = {
"levels": [
{
"level": 0,
"coordinates": [142, -60, -179],
"placement": [
["stone", "stone", "oak_door", "stone", "stone"],
["stone", "air", "air", "air", "stone"],
["stone", "air", "air", "air", "stone"],
["stone", "stone", "stone", "stone", "stone"]
]
},
{
"level": 1,
"coordinates": [142, -59, -179],
"placement": [
["stone", "stone", "air", "stone", "stone"],
["stone", "air", "air", "air", "stone"],
["stone", "air", "air", "air", "stone"],
["stone", "stone", "stone", "stone", "stone"]
]
},
{
"level": 2,
"coordinates": [142, -58, -179],
"placement": [
["stone", "stone", "stone", "stone", "stone"],
["stone", "stone", "stone", "stone", "stone"],
["stone", "stone", "stone", "stone", "stone"],
["stone", "stone", "stone", "stone", "stone"],
]
}
]
};
// have andy build the blueprint automatically
const result = autoBuild(blueprint);
// const result = clearHouse(blueprint)
const commands = result.commands;
const nearbyPosition = result.nearbyPosition;
for (const command of commands) {
bot.chat(command);
}
console.log(commands.slice(-10));
// Print out the location nearby the blueprint
console.log(`tp ${nearbyPosition.x} ${nearbyPosition.y} ${nearbyPosition.z}`)
});
/**
* Takes in the blueprint, and then converts it into a set of /setblock commands for the bot to follow
* @Returns: An object containing the setblock commands as a list of strings, and a position nearby the blueprint but not in it
* @param blueprint
*/
export function autoBuild(blueprint) {
const commands = [];
let minX = Infinity, maxX = -Infinity;
let minY = Infinity, maxY = -Infinity;
let minZ = Infinity, maxZ = -Infinity;
for (const level of blueprint.levels) {
// console.log(level.level)
const baseX = level.coordinates[0];
const baseY = level.coordinates[1];
const baseZ = level.coordinates[2];
const placement = level.placement;
// Update bounds
minX = Math.min(minX, baseX);
maxX = Math.max(maxX, baseX + placement[0].length - 1);
minY = Math.min(minY, baseY);
maxY = Math.max(maxY, baseY);
minZ = Math.min(minZ, baseZ);
maxZ = Math.max(maxZ, baseZ + placement.length - 1);
// Loop through the 2D placement array
for (let z = 0; z < placement.length; z++) {
for (let x = 0; x < placement[z].length; x++) {
const blockType = placement[z][x];
if (blockType) {
const setblockCommand = `/setblock ${baseX + x} ${baseY} ${baseZ + z} ${blockType}`;
commands.push(setblockCommand);
}
}
}
}
// Calculate a position nearby the blueprint but not in it
const nearbyPosition = {
x: maxX + 5, // Move 5 blocks to the right
y: minY, // Stay on the lowest level of the blueprint
z: minZ // Stay aligned with the front of the blueprint
};
return { commands, nearbyPosition};
}
/**
* Takes in a blueprint, and returns a set of commands to clear up the space.
*
*/
export function autoDelete(blueprint) {
const commands = [];
let minX = Infinity, maxX = -Infinity;
let minY = Infinity, maxY = -Infinity;
let minZ = Infinity, maxZ = -Infinity;
for (const level of blueprint.levels) {
const baseX = level.coordinates[0];
const baseY = level.coordinates[1];
const baseZ = level.coordinates[2];
const placement = level.placement;
// Update bounds
minX = Math.min(minX, baseX);
maxX = Math.max(maxX, baseX + placement[0].length - 1);
minY = Math.min(minY, baseY);
maxY = Math.max(maxY, baseY);
minZ = Math.min(minZ, baseZ);
maxZ = Math.max(maxZ, baseZ + placement.length - 1);
// Loop through the 2D placement array
for (let z = 0; z < placement.length; z++) {
for (let x = 0; x < placement[z].length; x++) {
const blockType = placement[z][x];
if (blockType) {
const setblockCommand = `/setblock ${baseX + x} ${baseY} ${baseZ + z} air`;
commands.push(setblockCommand);
}
}
}
}
// Calculate a position nearby the blueprint but not in it
const nearbyPosition = {
x: maxX + 5, // Move 5 blocks to the right
y: minY, // Stay on the lowest level of the blueprint
z: minZ // Stay aligned with the front of the blueprint
};
return { commands, nearbyPosition };
}

147
test/test_check_blocks.js Normal file
View file

@ -0,0 +1,147 @@
import mineflayer from 'mineflayer';
import { Vec3 } from 'vec3';
import { ConstructionTaskValidator, Blueprint } from '../src/agent/tasks.js';
import { Agent } from '../src/agent/agent.js';
const bot = mineflayer.createBot({
host: 'localhost', // Replace with your server IP or hostname
port: 55916, // Replace with your server port
username: 'andy', // Replace with your bot's username
// password: 'your_bot_password' // Only if the server has online-mode=true
});
bot.on('spawn', async () => {
bot.chat("/setblock ~ ~ ~ stone");
console.log("Bot spawned. Starting blueprint check...");
await new Promise((resolve) => setTimeout(resolve, 5000));
const blockAtLocation = await bot.blockAt(new Vec3(142, -60, -179));
console.log(blockAtLocation);
const blueprint = new Blueprint(blueprintData);
console.log(blueprint.explain());
console.log(blueprint.explainLevel(0));
try {
const check_level = await blueprint.checkLevel(bot, 0);
console.log(check_level);
let check_blueprint = await blueprint.check(bot);
console.log(check_blueprint);
let level_diff = await blueprint.explainLevelDifference(bot, 0);
console.log(level_diff);
let blueprint_diff = await blueprint.explainBlueprintDifference(bot);
console.log(blueprint_diff);
} catch (err) {
console.error("Error checking blueprint:", err);
}
// console.log(blueprint.checkLevel(bot, 0));
// console.log(blueprint.check(bot));
// console.log(blueprint.explainBlueprintDifference(bot, blueprintData));
// console.log(blueprint.explainLevelDifference(bot, 0));
bot.quit();
});
async function checkBluepint(bot, blueprintData) {
const materials = blueprintData.materials;
const levels = blueprintData.levels;
const mismatches = [];
const matches = [];
for (let i = 0; i < levels.length; i++) {
const levelData = levels[i];
const result = await checkLevelBlueprint(bot, levelData);
mismatches.push(...result.mismatches);
matches.push(...result.matches);
}
return {
"mismatches": mismatches,
"matches": matches
};
}
async function checkLevelBlueprint(bot, levelData) {
const startCoords = levelData.coordinates;
const placement = levelData.placement;
const mismatches = [];
const matches = [];
for (let zOffset = 0; zOffset < placement.length; zOffset++) {
const row = placement[zOffset];
for (let xOffset = 0; xOffset < row.length; xOffset++) {
const blockName = row[xOffset];
const x = startCoords[0] + xOffset;
const y = startCoords[1];
const z = startCoords[2] + zOffset;
try {
const blockAtLocation = await bot.blockAt(new Vec3(x, y, z));
if (!blockAtLocation || blockAtLocation.name !== blockName) {
mismatches.push({
level: levelData.level,
coordinates: [x, y, z],
expected: blockName,
actual: blockAtLocation ? bot.registry.blocks[blockAtLocation.type].name : 'air' // Assuming air if no block
});
} else {
matches.push({
level: levelData.level,
coordinates: [x, y, z],
expected: blockName,
actual: blockAtLocation ? bot.registry.blocks[blockAtLocation.type].name : 'air' // Assuming air if no block
});
}
} catch (err) {
console.error(`Error getting block at (${x}, ${y}, ${z}):`, err);
return false; // Stop checking if there's an issue getting blocks
}
}
}
return {
"mismatches": mismatches,
"matches": matches
};
}
const blueprintData = {
"materials": {
"oak_planks": 20,
"oak_door": 1,
"stone": 26,
},
"levels": [
{
"level": 0,
"coordinates": [142, -60, -179],
"placement":
[
["stone", "stone", "oak_door", "stone", "stone"],
["stone", "air", "air", "air", "stone"],
["stone", "air", "air", "air", "stone"],
["stone", "stone", "stone", "stone", "stone"]
]
},
{
"level": 1,
"coordinates": [142, -59, -179],
"placement":
[
["stone", "stone", "oak_door", "stone", "stone"],
["stone", "air", "air", "air", "stone"],
["stone", "air", "air", "air", "stone"],
["stone", "stone", "stone", "stone", "stone"]
]
},
{
"level": 2,
"coordinates": [142, -58, -179],
"placement":
[
["oak_planks", "oak_planks", "oak_planks", "oak_planks", "oak_planks"],
["oak_planks", "oak_planks", "oak_planks", "oak_planks", "oak_planks"],
["oak_planks", "oak_planks", "oak_planks", "oak_planks", "oak_planks"],
["oak_planks", "oak_planks", "oak_planks", "oak_planks", "oak_planks"]
]
}
]
};

View file

@ -0,0 +1,147 @@
import mineflayer from 'mineflayer';
import { Vec3 } from 'vec3';
async function checkBuildingBlueprint(bot, blueprint) {
/**
* Checks if a Minecraft building matches a given blueprint using Mineflayer.
*
* @param {mineflayer.Bot} bot - The Mineflayer bot instance.
* @param {object} blueprint - The blueprint object.
* @returns {Promise<boolean>} - True if the building matches, false otherwise.
*/
const mismatches = [];
for (const levelData of blueprint.levels) {
const levelNum = levelData.level;
const startCoords = levelData.coordinates;
const placement = levelData.placement;
for (let zOffset = 0; zOffset < placement.length; zOffset++) {
const row = placement[zOffset];
for (let xOffset = 0; xOffset < row.length; xOffset++) {
const blockName = row[xOffset];
const expectedBlockId = getBlockIdFromName(bot, blockName);
if (expectedBlockId === undefined) {
console.warn(`Warning: Unknown block name '${blockName}' in blueprint. Skipping.`);
continue;
}
const x = startCoords[0] + xOffset;
const y = startCoords[1] + levelNum;
const z = startCoords[2] + zOffset;
try {
const blockAtLocation = await bot.blockAt(new Vec3(x, y, z));
if (!blockAtLocation || blockAtLocation.type !== expectedBlockId) {
mismatches.push({
level: levelNum,
coordinates: [x, y, z],
expected: blockName,
actual: blockAtLocation ? bot.registry.blocks[blockAtLocation.type].name : 'air' // Assuming air if no block
});
}
} catch (err) {
console.error(`Error getting block at (${x}, ${y}, ${z}):`, err);
return false; // Stop checking if there's an issue getting blocks
}
}
}
}
if (mismatches.length > 0) {
console.log("Building does not match the blueprint. Found the following mismatches:");
mismatches.forEach(mismatch => {
console.log(` Level ${mismatch.level}, Coordinates ${mismatch.coordinates.join(', ')}: Expected '${mismatch.expected}', Found '${mismatch.actual}'`);
});
return false;
} else {
console.log("Building matches the blueprint!");
return true;
}
}
function getBlockIdFromName(bot, blockName) {
/**
* Gets the numerical block ID from a string block name using the bot's registry.
* @param {mineflayer.Bot} bot - The Mineflayer bot instance.
* @param {string} blockName - The name of the block (case-insensitive).
* @returns {number|undefined} - The block ID, or undefined if not found.
*/
const blockInfo = bot.registry.blocksByName[blockName.toLowerCase().replace(/ /g, '_')];
return blockInfo ? blockInfo.id : undefined;
}
// Example usage (replace with your bot login and server details)
const blueprintData = {
"materials": {
"plank": {
"id": "oak_plank",
"number": 20
},
"door": {
"id": "oak_door",
"number": 1
}
},
"levels": [
{
"level": 0,
"coordinates": [142, -60, -179],
"placement":
[
["stone", "stone", "oak_door", "stone", "stone"],
["stone", "air", "air", "air", "stone"],
["stone", "air", "air", "air", "stone"],
["stone", "stone", "stone", "stone", "stone"]
]
},
{
"level": 1,
"coordinates": [142, -59, -179],
"placement":
[
["stone", "stone", "air", "stone", "stone"],
["stone", "air", "air", "air", "stone"],
["stone", "air", "air", "air", "stone"],
["stone", "stone", "stone", "stone", "stone"]
]
},
{
"level": 2,
"coordinates": [142, -58, -179],
"placement":
[
["oak_plank", "oak_plank", "oak_plank", "oak_plank", "oak_plank"],
["oak_plank", "oak_plank", "oak_plank", "oak_plank", "oak_plank"],
["oak_plank", "oak_plank", "oak_plank", "oak_plank", "oak_plank"],
["oak_plank", "oak_plank", "oak_plank", "oak_plank", "oak_plank"]
]
}
]
};
const bot = mineflayer.createBot({
host: 'localhost', // Replace with your server IP or hostname
port: 55916, // Replace with your server port
username: 'andy', // Replace with your bot's username
// password: 'your_bot_password' // Only if the server has online-mode=true
});
bot.on('spawn', async () => {
console.log("Bot spawned. Starting blueprint check...");
const matchesBlueprint = await checkBuildingBlueprint(bot, blueprintData);
console.log(`Blueprint check result: ${matchesBlueprint}`);
bot.quit(); // Disconnect the bot after checking
});
bot.on('kicked', (reason, loggedIn) => {
console.log(`Bot kicked: ${reason}, loggedIn: ${loggedIn}`);
});
bot.on('error', err => {
console.log(`Bot error: ${err}`);
});

View file

@ -0,0 +1,821 @@
import {materials} from "mineflayer-armor-manager/dist/data/armor.js";
/**
*
* @param m - length (x-axis)
* @param n - width (y-axis)
* @param p - depth (z-axis, how many layers)
* @param rooms
* @returns {any[][][]}
*/
function generateAbstractRooms(m, n, p, rooms = 5) {
const matrix = Array.from({ length: p }, () =>
Array.from({ length: m }, () =>
Array(n).fill('air')
)
);
// Mark entire outer border with 'stone'
for (let z = 0; z < p; z++) {
for (let x = 0; x < m; x++) {
for (let y = 0; y < n; y++) {
if (
z === 0 || z === p - 1 || // Top and bottom faces
x === 0 || x === m - 1 || // Front and back faces
y === 0 || y === n - 1 // Left and right faces
) {
matrix[z][x][y] = 'stone';
}
}
}
}
const usedSpaces = new Set();
// Loop that places rooms
for (let roomCount = 0; roomCount < rooms; roomCount++) {
const length = Math.max(4, Math.floor(Math.random() * 6) + 4);
const width = Math.max(4, Math.floor(Math.random() * 6) + 4);
const depth = Math.max(3, Math.floor(Math.random() * 6) + 4);
let roomPlaced = false;
for (let attempt = 0; attempt < 50; attempt++) {
const x = Math.floor(Math.random() * (m - length - 1)) + 1;
const y = Math.floor(Math.random() * (n - width - 1)) + 1;
const z = Math.floor(Math.random() * (p - depth - 1)) + 1;
// Check space availability, excluding room's own edges (so that walls/ceilings can be shared)
console.log(`Attempting room: ${length}x${width}x${depth}`);
const spaceAvailable = !Array.from({ length: depth }).some((_, di) =>
Array.from({ length: length }).some((_, dj) =>
Array.from({ length: width }).some((_, dk) =>
// Exclude room's own edges from check
(di !== 0 && di !== depth - 1 &&
dj !== 0 && dj !== length - 1 &&
dk !== 0 && dk !== width - 1) &&
usedSpaces.has(`${x + dj},${y + dk},${z + di}`)
)
)
);
if (spaceAvailable) {
for (let di = 0; di < depth; di++) {
for (let dj = 0; dj < length; dj++) {
for (let dk = 0; dk < width; dk++) {
const spaceKey = `${x + dj},${y + dk},${z + di}`;
usedSpaces.add(spaceKey);
if (
z + di >= 0 && z + di < p &&
x + dj >= 0 && x + dj < m &&
y + dk >= 0 && y + dk < n
) {
// Mark only the outer edges of the room
if (di === 0 || di === depth - 1 ||
dj === 0 || dj === length - 1 ||
dk === 0 || dk === width - 1) {
matrix[z + di][x + dj][y + dk] = 'stone';
}
}
}
}
}
roomPlaced = true;
break;
}
}
if (!roomPlaced) {
console.warn(`Could not place room ${roomCount}`);
}
}
blueprint = matrixToBlueprint(matrix, [200, -60, -100])
return blueprint;
}
/**
* Systematically builds the houses by placing them next to the already existing rooms. Still uses randomness for what gets placed next.
* @param m width of the 3D space
* @param n height of the 3D space
* @param p depth of the 3D space
* @param rooms Number of rooms to attempt to generate
* @param minRoomWidth
* @param minRoomLength
* @param minRoomDepth
* @param roomVariance How much the room size will vary
* @param wrapping material of wrapping (air, glass, etc...) -> default is air
* @param carpetStyle 0,1,2 increasingly more complex
* @param windowStyle 0,1,2 increasingly more complex
* @param complexity 0,1,2,3,4 for increasingly complex materials for room generation
* @returns a Blueprint object
*/
// todo: room material params, roof style
function proceduralGeneration(m = 20,
n = 20,
p = 20,
rooms = 8,
minRoomWidth = 5,
minRoomLength = 5,
minRoomDepth = 6,
roomVariance = 5,
wrapping = "air",
carpetStyle = 1,
windowStyle = 2,
complexity = 4) {
// Build 3D space
const matrix = Array.from({length: p}, () =>
Array.from({length: m}, () =>
Array(n).fill('air')
)
);
// set materials
let roomMaterials = ["stone", "terracotta", "quartz_block", "copper_block", "purpur_block"]
if (complexity < roomMaterials.length){
roomMaterials = roomMaterials.slice(0, complexity + 1);
}
// Mark entire outer border with 'stone'
for (let z = 0; z < p; z++) {
for (let x = 0; x < m; x++) {
for (let y = 0; y < n; y++) {
if (
z === 0 || z === p - 1 || // Top and bottom faces
x === 0 || x === m - 1 || // Front and back faces
y === 0 || y === n - 1 // Left and right faces
) {
matrix[z][x][y] = 'stone';
}
}
}
}
// Replace outer layer with wrap
for (let z = 0; z < p; z++) {
for (let x = 0; x < m; x++) {
for (let y = 0; y < n; y++) {
if (
(z === p - 1 || // Top face
x === 0 || x === m - 1 || // Front and back faces
y === 0 || y === n - 1) // Left and right faces
) {
matrix[z][x][y] = wrapping;
}
}
}
}
let placedRooms = 0;
let lastRoom = null;
// Direction probabilities (e.g., 'above': 40%, 'left': 15%, etc.)
const directionChances = [
{direction: 'above', chance: 0.15},
{direction: 'left', chance: 0.15},
{direction: 'right', chance: 0.15},
{direction: 'forward', chance: 0.15},
{direction: 'backward', chance: 0.15},
];
// Function to pick a random direction based on percentages
function getRandomDirection() {
const rand = Math.random();
let cumulative = 0;
for (const {direction, chance} of directionChances) {
cumulative += chance;
if (rand <= cumulative) return direction;
}
return directionChances[1].direction; // Fallback to the first direction
}
// Ensures no rooms overlap except at edges
function isSpaceValid(newX, newY, newZ, newLength, newWidth, newDepth) {
for (let di = 0; di < newDepth; di++) {
for (let dj = 0; dj < newLength; dj++) {
for (let dk = 0; dk < newWidth; dk++) {
const x = newX + dj;
const y = newY + dk;
const z = newZ + di;
// Skip checking the outermost borders of the new room (these can overlap with stone)
if (dj === 0 || dj === newLength - 1 ||
dk === 0 || dk === newWidth - 1 ||
di === 0 || di === newDepth - 1) {
continue;
}
// For non-border spaces, ensure they're air
if (matrix[z][x][y] !== 'air') {
return false;
}
}
}
}
return true;
}
function validateAndBuildBorder(matrix, newX, newY, newZ, newLength, newWidth, newDepth, m, n, p, material) {
// Allow rooms to use the matrix edges (note the <= instead of <)
if (
newX >= 0 && newX + newLength <= m &&
newY >= 0 && newY + newWidth <= n &&
newZ >= 0 && newZ + newDepth <= p &&
isSpaceValid(newX, newY, newZ, newLength, newWidth, newDepth)
) {
console.log(`Placing room at (${newX}, ${newY}, ${newZ}) with dimensions (${newLength}x${newWidth}x${newDepth})`);
for (let di = 0; di < newDepth; di++) {
for (let dj = 0; dj < newLength; dj++) {
for (let dk = 0; dk < newWidth; dk++) {
const x = newX + dj;
const y = newY + dk;
const z = newZ + di;
// If this is at a matrix border, don't modify it
if (z === 0){
continue;
}
// if (x === 0 || x === m - 1 ||
// y === 0 || y === n - 1 ||
// z === 0 || z === p - 1) {
// continue;
// }
// For non-border spaces, check if this is a floor that should be shared
//was: === 'stone'
if (di === 0 && matrix[z-1][x][y] !== 'air') {
// Skip creating floor if there's a ceiling below
matrix[z][x][y] = 'air';
} else if (di === 0 || di === newDepth - 1 ||
dj === 0 || dj === newLength - 1 ||
dk === 0 || dk === newWidth - 1) {
matrix[z][x][y] = material;
} else {
matrix[z][x][y] = 'air';
}
}
}
}
return true;
}
return false;
}
function addDoor(matrix, x, y, z, material) {
matrix[z][x][y] = material;
// Place the lower half of the door
matrix[z + 1][x][y] = 'dark_oak_door[half=lower, hinge=left]';
// Place the upper half of the door
matrix[z + 2][x][y] = 'dark_oak_door[half=upper, hinge=left]';
}
// Takes in a room and randomly converts some faces to be windows
function addWindowsAsSquares(matrix, x, y, z, newLength, newWidth, newDepth, material) {
// Matrix dimensions
const matrixDepth = matrix.length;
const matrixLength = matrix[0].length;
const matrixWidth = matrix[0][0].length;
const windowX = Math.ceil(minRoomWidth/2)
const windowY = Math.ceil(minRoomLength/2)
const windowZ = Math.ceil(minRoomDepth/2)
// Helper function to check if coordinates are within bounds
function isInBounds(z, x, y) {
return z >= 0 && z < matrixDepth &&
x >= 0 && x < matrixLength &&
y >= 0 && y < matrixWidth;
}
// Front and back faces (z is constant)
if (Math.random() < 0.8) {
let centerX = x + Math.floor(newLength / 2 - windowX/2);
let centerY = y + Math.floor(newWidth / 2 - windowY/2);
for (let dx = 0; dx <= windowX; dx++) {
for (let dy = 0; dy <= windowY; dy++) {
let frontZ = z;
let backZ = z + newDepth - 1;
if (isInBounds(frontZ, centerX + dx, centerY + dy) &&
matrix[frontZ][centerX + dx][centerY + dy] === material) {
matrix[frontZ][centerX + dx][centerY + dy] = 'glass';
}
if (isInBounds(backZ, centerX + dx, centerY + dy) &&
matrix[backZ][centerX + dx][centerY + dy] === material) {
matrix[backZ][centerX + dx][centerY + dy] = 'glass';
}
}
}
}
// Left and right faces (x is constant)
if (Math.random() < 0.8) {
let centerZ = z + Math.floor(newDepth / 2 - windowZ/2);
let centerY = y + Math.floor(newWidth / 2 - windowY/2);
for (let dz = 0; dz <= windowZ; dz++) {
for (let dy = 0; dy <= windowY; dy++) {
let leftX = x;
let rightX = x + newLength - 1;
if (isInBounds(centerZ + dz, leftX, centerY + dy) &&
matrix[centerZ + dz][leftX][centerY + dy] === material) {
matrix[centerZ + dz][leftX][centerY + dy] = 'glass';
}
if (isInBounds(centerZ + dz, rightX, centerY + dy) &&
matrix[centerZ + dz][rightX][centerY + dy] === material) {
matrix[centerZ + dz][rightX][centerY + dy] = 'glass';
}
}
}
}
// Top and bottom faces (y is constant)
if (Math.random() < 0.8) {
let centerX = x + Math.floor(newLength / 2 - windowX/2);
let centerZ = z + Math.floor(newDepth / 2 - windowZ / 2);
for (let dx = 0; dx <= windowX; dx++) {
for (let dz = 0; dz <= windowZ; dz++) {
let bottomY = y;
let topY = y + newWidth - 1;
if (isInBounds(centerZ + dz, centerX + dx, bottomY) &&
matrix[centerZ + dz][centerX + dx][bottomY] === material) {
matrix[centerZ + dz][centerX + dx][bottomY] = 'glass';
}
if (isInBounds(centerZ + dz, centerX + dx, topY) &&
matrix[centerZ + dz][centerX + dx][topY] === material) {
matrix[centerZ + dz][centerX + dx][topY] = 'glass';
}
}
}
}
}
function addWindowsAsPlane(matrix, x, y, z, newLength, newWidth, newDepth, material) {
// Ensure the new dimensions are within bounds
const maxX = matrix[0].length;
const maxY = matrix[0][0].length;
const maxZ = matrix.length;
// Each face has a 30% chance of becoming a window
if (Math.random() < 0.8) {
for (let dx = 0; dx < newLength; dx++) {
for (let dy = 0; dy < newWidth; dy++) {
let frontZ = z;
let backZ = z + newDepth - 1;
// Check bounds before modifying the matrix
if (frontZ >= 0 && frontZ < maxZ && x + dx >= 0 && x + dx < maxX && y + dy >= 0 && y + dy < maxY) {
if (matrix[frontZ][x + dx][y + dy] === material) {
matrix[frontZ][x + dx][y + dy] = 'glass';
}
}
if (backZ >= 0 && backZ < maxZ && x + dx >= 0 && x + dx < maxX && y + dy >= 0 && y + dy < maxY) {
if (matrix[backZ][x + dx][y + dy] === material) {
matrix[backZ][x + dx][y + dy] = 'glass';
}
}
}
}
}
if (Math.random() < 0.8) {
for (let dz = 0; dz < newDepth; dz++) {
for (let dy = 0; dy < newWidth; dy++) {
let leftX = x;
let rightX = x + newLength - 1;
// Check bounds before modifying the matrix
if (leftX >= 0 && leftX < maxX && z + dz >= 0 && z + dz < maxZ && y + dy >= 0 && y + dy < maxY) {
if (matrix[z + dz][leftX][y + dy] === material) {
matrix[z + dz][leftX][y + dy] = 'glass';
}
}
if (rightX >= 0 && rightX < maxX && z + dz >= 0 && z + dz < maxZ && y + dy >= 0 && y + dy < maxY) {
if (matrix[z + dz][rightX][y + dy] === material) {
matrix[z + dz][rightX][y + dy] = 'glass';
}
}
}
}
}
}
// out of commission
function addStairs(matrix, x, y, z, direction) {
let dz = 0; // Change in Z direction
let dx = 0; // Change in X direction
let facing = '';
// Determine direction and facing
switch (direction) {
case 'north':
dz = -1;
facing = 'oak_stairs[facing=north]';
break;
case 'south':
dz = 1;
facing = 'oak_stairs[facing=south]';
break;
case 'east':
dx = 1;
facing = 'oak_stairs[facing=east]';
break;
case 'west':
dx = -1;
facing = 'oak_stairs[facing=west]';
break;
default:
console.error('Invalid stair direction');
return;
}
// Bore stair pattern downwards until we hit a floor or the matrix edge
let currentZ = z;
while (currentZ > 0 && matrix[currentZ - 1][x][y] === 'air') {
// Place stone as foundation
matrix[currentZ - 1][x][y] = 'stone';
// Place stair above the stone
matrix[currentZ][x][y] = facing;
// Move down diagonally
x += dx;
y += dz;
currentZ--;
// Check if we've hit the edge
if (x < 0 || x >= matrix[0].length || y < 0 || y >= matrix[0][0].length) break;
}
}
function addCarpet(probability, matrix, newX, newY, newZ, newLength, newWidth, material) {
let colors = ["blue", "cyan", "light_blue", "lime"];
// Iterate through the dimensions of the room
for (let dx = 1; dx < newLength-1; dx++) {
for (let dy = 1; dy < newWidth-1; dy++) {
let x = newX + dx;
let y = newY + dy;
let z = newZ; // Start at floor level
// Check if there is floor (not air)
if (matrix[z][x][y] === material) {
// Consider a random probability of adding a carpet
if (Math.random() < probability) {
// Choose a random color for the carpet
let randomColor = colors[Math.floor(Math.random() * colors.length)];
// Add carpet one z position above the floor with a random color
matrix[z + 1][x][y] = `${randomColor}_carpet`;
}
}
}
}
}
function addLadder(matrix, x, y, z) {
let currentZ = z+1;
// turn the floor into air where person would go up
matrix[currentZ][x+1][y] = 'air';
// Build the first 3 ladder segments from floor level downwards
for (let i = 0; i < 3; i++) {
matrix[currentZ][x][y] = 'ladder[facing=north]';
currentZ-=1
}
// Continue building ladder downwards until a floor is hit or we reach the bottom
while (currentZ >= 0 && matrix[currentZ][x][y] === 'air') {
// Place ladder
matrix[currentZ][x][y] = 'ladder[facing=north]';
// Move down
currentZ--;
}
}
function embellishments(carpet, windowStyle, matrix, newX, newY, newZ, newLength, newWidth, newDepth, material){
switch (windowStyle) {
case 0:
break;
case 1:
addWindowsAsSquares(matrix, newZ, newY, newZ, newLength, newWidth, newDepth, material)
break;
case 2:
addWindowsAsPlane(matrix, newZ, newY, newZ, newLength, newWidth, newDepth, material)
}
switch (carpet) {
case 0:
break;
case 1:
addCarpet(0.3,matrix,newX, newY, newZ, newLength, newWidth, material);
break;
case 2:
addCarpet(0.7,matrix,newX, newY, newZ, newLength, newWidth, material)
break;
}
}
// Places rooms until we can't, or we place all
// attempts random configurations of rooms in random directions.
while (placedRooms < rooms) {
let roomPlaced = false;
for (let attempt = 0; attempt < 150; attempt++) {
const material = roomMaterials[Math.floor(Math.random() * roomMaterials.length)];
// dimensions of room
const newLength = Math.max(minRoomLength, Math.floor(Math.random() * roomVariance) + minRoomLength);
const newWidth = Math.max(minRoomWidth, Math.floor(Math.random() * roomVariance) + minRoomWidth);
const newDepth = Math.max(minRoomDepth, Math.floor(Math.random() * Math.floor(roomVariance/2) ) + minRoomDepth );
let newX, newY, newZ;
// first room is special
if (placedRooms === 0) {
// First room placement
newX = Math.floor(Math.random() * (m - newLength - 1)) + 1;
newY = Math.floor(Math.random() * (n - newWidth - 1)) + 1;
newZ = 0; // Ground floor
if (validateAndBuildBorder(matrix, newX, newY, newZ, newLength, newWidth, newDepth, m, n, p, material)) {
lastRoom = { x: newX, y: newY, z: newZ, length: newLength, width: newWidth, depth: newDepth };
roomPlaced = true;
placedRooms++;
// Add doors to all four sides
// Left side
addDoor(matrix, newX, newY + Math.floor(newWidth / 2), newZ, material);
// Right side
addDoor(matrix, newX + newLength - 1, newY + Math.floor(newWidth / 2), newZ, material);
// Front side
addDoor(matrix, newX + Math.floor(newLength / 2), newY, newZ, material);
// Back side
addDoor(matrix, newX + Math.floor(newLength / 2), newY + newWidth - 1, newZ, material);
addCarpet(0.7, matrix, newX, newY, newZ, newLength, newWidth)
}
break;
}
else {
const direction = getRandomDirection();
switch (direction) {
case 'above':
newX = lastRoom.x;
newY = lastRoom.y;
newZ = lastRoom.z + lastRoom.depth - 1;
if (validateAndBuildBorder(matrix, newX, newY, newZ, newLength, newWidth, newDepth, m, n, p, material)) {
embellishments(carpetStyle, windowStyle, matrix, newX, newY, newZ, newLength, newWidth, newDepth, material)
addLadder(matrix, lastRoom.x + Math.floor(lastRoom.length / 2),
lastRoom.y + Math.floor(lastRoom.width / 2),
newZ); // Adding the ladder
lastRoom = { x: newX, y: newY, z: newZ, length: newLength, width: newWidth, depth: newDepth };
roomPlaced = true;
placedRooms++;
break;
}
break;
case 'left':
newX = lastRoom.x - newLength + 1;
newY = lastRoom.y;
newZ = lastRoom.z;
if (validateAndBuildBorder(matrix, newX, newY, newZ, newLength, newWidth, newDepth, m, n, p, material)) {
embellishments(carpetStyle, windowStyle, matrix, newX, newY, newZ, newLength, newWidth, newDepth, material)
addDoor(matrix, lastRoom.x, lastRoom.y + Math.floor(lastRoom.width / 2), lastRoom.z, material);
lastRoom = { x: newX, y: newY, z: newZ, length: newLength, width: newWidth, depth: newDepth };
roomPlaced = true;
placedRooms++;
break;
}
break;
case 'right':
newX = lastRoom.x + lastRoom.length - 1;
newY = lastRoom.y;
newZ = lastRoom.z;
if (validateAndBuildBorder(matrix, newX, newY, newZ, newLength, newWidth, newDepth, m, n, p, material)) {
embellishments(carpetStyle, windowStyle, matrix, newX, newY, newZ, newLength, newWidth, newDepth, material)
addDoor(matrix, lastRoom.x + lastRoom.length - 1,
lastRoom.y + Math.floor(lastRoom.width / 2),
lastRoom.z, material);
lastRoom = { x: newX, y: newY, z: newZ, length: newLength, width: newWidth, depth: newDepth };
roomPlaced = true;
placedRooms++;
break;
}
break;
case 'forward':
newX = lastRoom.x;
newY = lastRoom.y + lastRoom.width - 1;
newZ = lastRoom.z;
if (validateAndBuildBorder(matrix, newX, newY, newZ, newLength, newWidth, newDepth, m, n, p, material)) {
embellishments(carpetStyle, windowStyle, matrix, newX, newY, newZ, newLength, newWidth, newDepth, material)
addDoor(matrix, lastRoom.x + Math.floor(lastRoom.length / 2),
lastRoom.y + lastRoom.width - 1,
lastRoom.z, material);
lastRoom = { x: newX, y: newY, z: newZ, length: newLength, width: newWidth, depth: newDepth };
roomPlaced = true;
placedRooms++;
break;
}
break;
case 'backward':
newX = lastRoom.x;
newY = lastRoom.y - newWidth + 1;
newZ = lastRoom.z;
if (validateAndBuildBorder(matrix, newX, newY, newZ, newLength, newWidth, newDepth, m, n, p, material)) {
embellishments(carpetStyle, windowStyle, matrix, newX, newY, newZ, newLength, newWidth, newDepth, material)
addDoor(matrix, lastRoom.x + Math.floor(lastRoom.length / 2),
lastRoom.y,
lastRoom.z, material);
lastRoom = { x: newX, y: newY, z: newZ, length: newLength, width: newWidth, depth: newDepth };
roomPlaced = true;
placedRooms++;
break;
}
break;
}
if (roomPlaced) {
break;
}
}
}
if (!roomPlaced) {
console.warn(`Could not place room ${placedRooms + 1}`);
break;
}
}
return matrix
}
/**
* Converts a 3D matrix into a Minecraft blueprint format
* @param {Array<Array<Array<string>>>} matrix - 3D matrix of block types
* @param {number[]} startCoord - Starting coordinates [x, y, z]
* @returns {Object} Blueprint object in Minecraft format
*/
function matrixToBlueprint(matrix, startCoord) {
// Validate inputs
if (!Array.isArray(matrix) || !Array.isArray(startCoord) || startCoord.length !== 3) {
throw new Error('Invalid input format');
}
const [startX, startY, startZ] = startCoord;
return {
levels: matrix.map((level, levelIndex) => ({
level: levelIndex,
coordinates: [
startX,
startY + levelIndex,
startZ
],
placement: level.map(row =>
// Ensure each block is a string, default to 'air' if undefined
row.map(block => block?.toString() || 'air')
)
}))
};
}
/**
* for cutesy output
* @param matrix
*/
function printMatrix(matrix) {
matrix.forEach((layer, layerIndex) => {
console.log(`Layer ${layerIndex}:`);
layer.forEach(row => {
console.log(
row.map(cell => {
switch (cell) {
case 'stone': return '█'; // Wall
case 'air': return '.'; // Open space
case 'dark_oak_door[half=upper, hinge=left]': return 'D';
case 'dark_oak_door[half=lower, hinge=left]': return 'D';
case 'oak_stairs[facing=north]': return 'S'; // Stairs
case 'oak_stairs[facing=east]': return 'S'; // Stairs
case 'oak_stairs[facing=south]': return 'S'; // Stairs
case 'oak_stairs[facing=west]': return 'S'; // Stairs
case 'glass': return 'W'
default: return '?'; // Unknown or unmarked space
}
}).join(' ')
);
});
console.log('---');
});
}
// main:
const resultMatrix = proceduralGeneration(20, 10, 20, 10, "air", 2, 2, 4);
printMatrix(resultMatrix)
let blueprint = matrixToBlueprint(resultMatrix,[122, -60, -178])
import mineflayer from "mineflayer";
import {autoBuild} from "./test_blueprint_layout.js";
const bot = mineflayer.createBot({
host: 'localhost', // Replace with your server IP or hostname
port: 55916, // Replace with your server port
username: 'andy', // Replace with your bot's username
// password: 'your_bot_password' // Only if the server has online-mode=true
});
bot.on('spawn', async () => {
// have andy build the blueprint automatically
const result = autoBuild(blueprint);
// const result = clearHouse(blueprint)
const commands = result.commands;
const nearbyPosition = result.nearbyPosition;
for (const command of commands) {
bot.chat(command);
}
console.log(commands.slice(-10));
bot.chat('I have built the house!');
// bot.chat('/tp @a ' + nearbyPosition.x + ' ' + nearbyPosition.y + ' ' + nearbyPosition.z+1);
// Print out the location nearby the blueprint
console.log(`tp ${nearbyPosition.x} ${nearbyPosition.y} ${nearbyPosition.z}`)
});

14
test/test_reset.js Normal file
View file

@ -0,0 +1,14 @@
import { resetConstructionWorld } from "../src/agent/tasks.js";
import mineflayer from 'mineflayer';
const bot = mineflayer.createBot({
host: 'localhost', // Replace with your server IP or hostname
port: 55916, // Replace with your server port
username: 'andy', // Replace with your bot's username
// password: 'your_bot_password' // Only if the server has online-mode=true
});
bot.on('spawn', async () => {
bot.chat("/tp andy izzycw");
resetConstructionWorld(bot);
});