mindcraft 0.1.4-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/FAQ.md +38 -0
- package/LICENSE +21 -0
- package/README.md +255 -0
- package/andy.json +6 -0
- package/bin/mindcraft.js +80 -0
- package/keys.example.json +19 -0
- package/main.js +80 -0
- package/package.json +78 -0
- package/patches/minecraft-data+3.97.0.patch +13 -0
- package/patches/mineflayer+4.33.0.patch +54 -0
- package/patches/mineflayer-pathfinder+2.4.5.patch +265 -0
- package/patches/mineflayer-pvp+1.3.2.patch +13 -0
- package/patches/prismarine-viewer+1.33.0.patch +13 -0
- package/patches/protodef+1.19.0.patch +15 -0
- package/profiles/andy-4-reasoning.json +14 -0
- package/profiles/andy-4.json +7 -0
- package/profiles/azure.json +19 -0
- package/profiles/claude.json +7 -0
- package/profiles/claude_thinker.json +15 -0
- package/profiles/deepseek.json +7 -0
- package/profiles/defaults/_default.json +256 -0
- package/profiles/defaults/assistant.json +14 -0
- package/profiles/defaults/creative.json +14 -0
- package/profiles/defaults/god_mode.json +14 -0
- package/profiles/defaults/survival.json +14 -0
- package/profiles/freeguy.json +7 -0
- package/profiles/gemini.json +9 -0
- package/profiles/gpt.json +12 -0
- package/profiles/grok.json +7 -0
- package/profiles/llama.json +10 -0
- package/profiles/mercury.json +9 -0
- package/profiles/mistral.json +5 -0
- package/profiles/qwen.json +17 -0
- package/profiles/tasks/construction_profile.json +42 -0
- package/profiles/tasks/cooking_profile.json +11 -0
- package/profiles/tasks/crafting_profile.json +71 -0
- package/profiles/vllm.json +10 -0
- package/settings.js +64 -0
- package/src/agent/action_manager.js +177 -0
- package/src/agent/agent.js +561 -0
- package/src/agent/coder.js +229 -0
- package/src/agent/commands/actions.js +504 -0
- package/src/agent/commands/index.js +259 -0
- package/src/agent/commands/queries.js +347 -0
- package/src/agent/connection_handler.js +96 -0
- package/src/agent/conversation.js +353 -0
- package/src/agent/history.js +122 -0
- package/src/agent/library/full_state.js +89 -0
- package/src/agent/library/index.js +23 -0
- package/src/agent/library/lockdown.js +32 -0
- package/src/agent/library/skill_library.js +93 -0
- package/src/agent/library/skills.js +2093 -0
- package/src/agent/library/world.js +431 -0
- package/src/agent/memory_bank.js +25 -0
- package/src/agent/mindserver_proxy.js +136 -0
- package/src/agent/modes.js +446 -0
- package/src/agent/npc/build_goal.js +80 -0
- package/src/agent/npc/construction/dirt_shelter.json +38 -0
- package/src/agent/npc/construction/large_house.json +230 -0
- package/src/agent/npc/construction/small_stone_house.json +42 -0
- package/src/agent/npc/construction/small_wood_house.json +42 -0
- package/src/agent/npc/controller.js +261 -0
- package/src/agent/npc/data.js +50 -0
- package/src/agent/npc/item_goal.js +355 -0
- package/src/agent/npc/utils.js +126 -0
- package/src/agent/self_prompter.js +146 -0
- package/src/agent/settings.js +7 -0
- package/src/agent/speak.js +150 -0
- package/src/agent/tasks/construction_tasks.js +1104 -0
- package/src/agent/tasks/cooking_tasks.js +358 -0
- package/src/agent/tasks/tasks.js +594 -0
- package/src/agent/templates/execTemplate.js +6 -0
- package/src/agent/templates/lintTemplate.js +10 -0
- package/src/agent/vision/browser_viewer.js +8 -0
- package/src/agent/vision/camera.js +78 -0
- package/src/agent/vision/vision_interpreter.js +82 -0
- package/src/mindcraft/index.js +28 -0
- package/src/mindcraft/mcserver.js +154 -0
- package/src/mindcraft/mindcraft.js +111 -0
- package/src/mindcraft/mindserver.js +328 -0
- package/src/mindcraft/public/index.html +1253 -0
- package/src/mindcraft/public/settings_spec.json +145 -0
- package/src/mindcraft/userconfig.js +72 -0
- package/src/mindcraft-py/example.py +27 -0
- package/src/mindcraft-py/init-mindcraft.js +24 -0
- package/src/mindcraft-py/mindcraft.py +99 -0
- package/src/models/_model_map.js +89 -0
- package/src/models/azure.js +32 -0
- package/src/models/cerebras.js +61 -0
- package/src/models/claude.js +87 -0
- package/src/models/deepseek.js +59 -0
- package/src/models/gemini.js +176 -0
- package/src/models/glhf.js +71 -0
- package/src/models/gpt.js +147 -0
- package/src/models/grok.js +82 -0
- package/src/models/groq.js +95 -0
- package/src/models/huggingface.js +86 -0
- package/src/models/hyperbolic.js +114 -0
- package/src/models/lmstudio.js +74 -0
- package/src/models/mercury.js +95 -0
- package/src/models/mistral.js +94 -0
- package/src/models/novita.js +71 -0
- package/src/models/ollama.js +115 -0
- package/src/models/openrouter.js +77 -0
- package/src/models/prompter.js +366 -0
- package/src/models/qwen.js +80 -0
- package/src/models/replicate.js +60 -0
- package/src/models/vllm.js +81 -0
- package/src/process/agent_process.js +84 -0
- package/src/process/init_agent.js +54 -0
- package/src/utils/examples.js +83 -0
- package/src/utils/keys.js +34 -0
- package/src/utils/math.js +13 -0
- package/src/utils/mcdata.js +572 -0
- package/src/utils/text.js +78 -0
- package/src/utils/translator.js +30 -0
|
@@ -0,0 +1,1104 @@
|
|
|
1
|
+
import {Vec3} from 'vec3';
|
|
2
|
+
|
|
3
|
+
export class ConstructionTaskValidator {
|
|
4
|
+
constructor(data, agent) {
|
|
5
|
+
this.blueprint = new Blueprint(data.blueprint);
|
|
6
|
+
this.agent = agent;
|
|
7
|
+
}
|
|
8
|
+
validate() {
|
|
9
|
+
try {
|
|
10
|
+
//todo: somehow make this more of a percentage or something
|
|
11
|
+
// console.log('Validating task...');
|
|
12
|
+
let valid = false;
|
|
13
|
+
let score = 0;
|
|
14
|
+
let result = this.blueprint.check(this.agent.bot);
|
|
15
|
+
if (result.mismatches.length === 0) {
|
|
16
|
+
valid = true;
|
|
17
|
+
console.log('Task is complete');
|
|
18
|
+
}
|
|
19
|
+
let total_blocks = result.mismatches.length + result.matches.length;
|
|
20
|
+
score = (result.matches.length / total_blocks) * 100;
|
|
21
|
+
console.log(`Task score: ${score}%`);
|
|
22
|
+
return {
|
|
23
|
+
"valid": valid,
|
|
24
|
+
"score": score
|
|
25
|
+
};
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error('Error validating task:', error);
|
|
28
|
+
return {
|
|
29
|
+
"valid": false,
|
|
30
|
+
"score": 0
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function resetConstructionWorld(bot, blueprint) {
|
|
37
|
+
console.log('Resetting world...');
|
|
38
|
+
const starting_position = blueprint.levels[0].coordinates;
|
|
39
|
+
const length = blueprint.levels[0].placement.length + 5;
|
|
40
|
+
const height = blueprint.levels.length + 5;
|
|
41
|
+
const width = blueprint.levels[0].placement[0].length + 5;
|
|
42
|
+
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`;
|
|
43
|
+
bot.chat(command);
|
|
44
|
+
console.log('World reset');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function checkLevelBlueprint(agent, levelNum) {
|
|
48
|
+
const blueprint = agent.task.blueprint;
|
|
49
|
+
const bot = agent.bot;
|
|
50
|
+
const result = blueprint.checkLevel(bot, levelNum);
|
|
51
|
+
if (result.mismatches.length === 0) {
|
|
52
|
+
return `Level ${levelNum} is correct`;
|
|
53
|
+
} else {
|
|
54
|
+
let explanation = blueprint.explainLevelDifference(bot, levelNum);
|
|
55
|
+
return explanation;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function checkBlueprint(agent) {
|
|
60
|
+
console.log('Checking blueprint...');
|
|
61
|
+
console.log(agent);
|
|
62
|
+
const blueprint = agent.task.blueprint;
|
|
63
|
+
const bot = agent.bot;
|
|
64
|
+
const result = blueprint.check(bot);
|
|
65
|
+
if (result.mismatches.length === 0) {
|
|
66
|
+
return "Blueprint is correct";
|
|
67
|
+
} else {
|
|
68
|
+
let explanation = blueprint.explainBlueprintDifference(bot);
|
|
69
|
+
return explanation;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export class Blueprint {
|
|
74
|
+
constructor(blueprint) {
|
|
75
|
+
this.data = blueprint;
|
|
76
|
+
}
|
|
77
|
+
explain() {
|
|
78
|
+
var explanation = "";
|
|
79
|
+
|
|
80
|
+
for (let item of this.data.levels) {
|
|
81
|
+
var coordinates = item.coordinates;
|
|
82
|
+
explanation += `Level ${item.level}: `;
|
|
83
|
+
explanation += `Start at coordinates X: ${coordinates[0]}, Y: ${coordinates[1]}, Z: ${coordinates[2]}`;
|
|
84
|
+
// let placement_string = this._getPlacementString(item.placement);
|
|
85
|
+
// explanation += `\n${placement_string}\n`;
|
|
86
|
+
}
|
|
87
|
+
return explanation;
|
|
88
|
+
}
|
|
89
|
+
_getPlacementString(placement) {
|
|
90
|
+
var placement_string = "[\n";
|
|
91
|
+
for (let row of placement) {
|
|
92
|
+
placement_string += "[";
|
|
93
|
+
for (let i = 0; i < row.length - 1; i++) {
|
|
94
|
+
let item = row[i];
|
|
95
|
+
placement_string += `${item}, `;
|
|
96
|
+
}
|
|
97
|
+
let final_item = row[row.length - 1];
|
|
98
|
+
placement_string += `${final_item}],\n`;
|
|
99
|
+
}
|
|
100
|
+
placement_string += "]";
|
|
101
|
+
return placement_string;
|
|
102
|
+
}
|
|
103
|
+
explainLevel(levelNum) {
|
|
104
|
+
const levelData = this.data.levels[levelNum];
|
|
105
|
+
var explanation = `Level ${levelData.level} `;
|
|
106
|
+
explanation += `starting at coordinates X: ${levelData.coordinates[0]}, Y: ${levelData.coordinates[1]}, Z: ${levelData.coordinates[2]}`;
|
|
107
|
+
let placement_string = this._getPlacementString(levelData.placement);
|
|
108
|
+
explanation += `\n${placement_string}\n`;
|
|
109
|
+
return explanation;
|
|
110
|
+
}
|
|
111
|
+
explainBlueprintDifference(bot) {
|
|
112
|
+
var explanation = "";
|
|
113
|
+
const levels = this.data.levels;
|
|
114
|
+
for (let i = 0; i < levels.length; i++) {
|
|
115
|
+
let level_explanation = this.explainLevelDifference(bot, i);
|
|
116
|
+
explanation += level_explanation + "\n";
|
|
117
|
+
}
|
|
118
|
+
return explanation;
|
|
119
|
+
}
|
|
120
|
+
explainLevelDifference(bot, levelNum) {
|
|
121
|
+
const results = this.checkLevel(bot, levelNum);
|
|
122
|
+
const mismatches = results.mismatches;
|
|
123
|
+
const levelData = this.data.levels[levelNum];
|
|
124
|
+
|
|
125
|
+
if (mismatches.length === 0) {
|
|
126
|
+
return `Level ${levelData.level} is complete`;
|
|
127
|
+
}
|
|
128
|
+
var explanation = `Level ${levelData.level} `;
|
|
129
|
+
// explanation += `at coordinates X: ${levelData.coordinates[0]}, Y: ${levelData.coordinates[1]}, Z: ${levelData.coordinates[2]}`;
|
|
130
|
+
explanation += " requires the following fixes:\n";
|
|
131
|
+
for (let item of mismatches) {
|
|
132
|
+
if (item.actual === 'air') {
|
|
133
|
+
explanation += `Place ${item.expected} at coordinates X: ${item.coordinates[0]}, Y: ${item.coordinates[1]}, Z: ${item.coordinates[2]}\n`;
|
|
134
|
+
} else if (item.expected === 'air') {
|
|
135
|
+
explanation += `Remove the ${item.actual} at coordinates X: ${item.coordinates[0]}, Y: ${item.coordinates[1]}, Z: ${item.coordinates[2]}\n`;
|
|
136
|
+
} else {
|
|
137
|
+
explanation += `Replace the ${item.actual} with a ${item.expected} at coordinates X: ${item.coordinates[0]}, Y: ${item.coordinates[1]}, Z: ${item.coordinates[2]} \n`;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return explanation;
|
|
141
|
+
}
|
|
142
|
+
check(bot) {
|
|
143
|
+
if (!bot || typeof bot !== 'object' || !bot.hasOwnProperty('blockAt')) {
|
|
144
|
+
throw new Error('Invalid bot object. Expected a mineflayer bot.');
|
|
145
|
+
}
|
|
146
|
+
const levels = this.data.levels;
|
|
147
|
+
const mismatches = [];
|
|
148
|
+
const matches = [];
|
|
149
|
+
for (let i = 0; i < levels.length; i++) {
|
|
150
|
+
const result = this.checkLevel(bot, i);
|
|
151
|
+
mismatches.push(...result.mismatches);
|
|
152
|
+
matches.push(...result.matches);
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
"mismatches": mismatches,
|
|
156
|
+
"matches": matches
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
checkLevel(bot, levelNum) {
|
|
160
|
+
const levelData = this.data.levels[levelNum];
|
|
161
|
+
const startCoords = levelData.coordinates;
|
|
162
|
+
const placement = levelData.placement;
|
|
163
|
+
const mismatches = [];
|
|
164
|
+
const matches = [];
|
|
165
|
+
|
|
166
|
+
for (let zOffset = 0; zOffset < placement.length; zOffset++) {
|
|
167
|
+
const row = placement[zOffset];
|
|
168
|
+
for (let xOffset = 0; xOffset < row.length; xOffset++) {
|
|
169
|
+
const blockName = row[xOffset];
|
|
170
|
+
|
|
171
|
+
const x = startCoords[0] + xOffset;
|
|
172
|
+
const y = startCoords[1];
|
|
173
|
+
const z = startCoords[2] + zOffset;
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
const blockAtLocation = bot.blockAt(new Vec3(x, y, z));
|
|
177
|
+
const actualBlockName = blockAtLocation ? bot.registry.blocks[blockAtLocation.type].name : "air";
|
|
178
|
+
|
|
179
|
+
// Skip if both expected and actual block are air
|
|
180
|
+
if (blockName === "air" && actualBlockName === "air") {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (actualBlockName !== blockName) {
|
|
185
|
+
mismatches.push({
|
|
186
|
+
level: levelData.level,
|
|
187
|
+
coordinates: [x, y, z],
|
|
188
|
+
expected: blockName,
|
|
189
|
+
actual: actualBlockName
|
|
190
|
+
});
|
|
191
|
+
} else {
|
|
192
|
+
matches.push({
|
|
193
|
+
level: levelData.level,
|
|
194
|
+
coordinates: [x, y, z],
|
|
195
|
+
expected: blockName,
|
|
196
|
+
actual: actualBlockName
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
} catch (err) {
|
|
200
|
+
console.error(`Error getting block at (${x}, ${y}, ${z}):`, err);
|
|
201
|
+
return false; // Stop checking if there's an issue getting blocks
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
"mismatches": mismatches,
|
|
207
|
+
"matches": matches
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Takes in the blueprint, and then converts it into a set of /setblock commands for the bot to follow
|
|
213
|
+
* @Returns: An object containing the setblock commands as a list of strings, and a position nearby the blueprint but not in it
|
|
214
|
+
* @param blueprint
|
|
215
|
+
*/
|
|
216
|
+
autoBuild() {
|
|
217
|
+
const commands = [];
|
|
218
|
+
let blueprint = this.data
|
|
219
|
+
|
|
220
|
+
let minX = Infinity, maxX = -Infinity;
|
|
221
|
+
let minY = Infinity, maxY = -Infinity;
|
|
222
|
+
let minZ = Infinity, maxZ = -Infinity;
|
|
223
|
+
|
|
224
|
+
for (const level of blueprint.levels) {
|
|
225
|
+
console.log(level.level)
|
|
226
|
+
const baseX = level.coordinates[0];
|
|
227
|
+
const baseY = level.coordinates[1];
|
|
228
|
+
const baseZ = level.coordinates[2];
|
|
229
|
+
const placement = level.placement;
|
|
230
|
+
|
|
231
|
+
// Update bounds
|
|
232
|
+
minX = Math.min(minX, baseX);
|
|
233
|
+
maxX = Math.max(maxX, baseX + placement[0].length - 1);
|
|
234
|
+
minY = Math.min(minY, baseY);
|
|
235
|
+
maxY = Math.max(maxY, baseY);
|
|
236
|
+
minZ = Math.min(minZ, baseZ);
|
|
237
|
+
maxZ = Math.max(maxZ, baseZ + placement.length - 1);
|
|
238
|
+
|
|
239
|
+
// Loop through the 2D placement array
|
|
240
|
+
for (let z = 0; z < placement.length; z++) {
|
|
241
|
+
for (let x = 0; x < placement[z].length; x++) {
|
|
242
|
+
const blockType = placement[z][x];
|
|
243
|
+
if (blockType) {
|
|
244
|
+
const setblockCommand = `/setblock ${baseX + x} ${baseY} ${baseZ + z} ${blockType}`;
|
|
245
|
+
commands.push(setblockCommand);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Calculate a position nearby the blueprint but not in it
|
|
252
|
+
const nearbyPosition = {
|
|
253
|
+
x: maxX + 5, // Move 5 blocks to the right
|
|
254
|
+
y: minY, // Stay on the lowest level of the blueprint
|
|
255
|
+
z: minZ // Stay aligned with the front of the blueprint
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
return { commands, nearbyPosition };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Takes in a blueprint, and returns a set of commands to clear up the space.
|
|
264
|
+
*
|
|
265
|
+
*/
|
|
266
|
+
autoDelete() {
|
|
267
|
+
console.log("auto delete called!")
|
|
268
|
+
const commands = [];
|
|
269
|
+
let blueprint = this.data
|
|
270
|
+
|
|
271
|
+
let minX = Infinity, maxX = -Infinity;
|
|
272
|
+
let minY = Infinity, maxY = -Infinity;
|
|
273
|
+
let minZ = Infinity, maxZ = -Infinity;
|
|
274
|
+
|
|
275
|
+
for (const level of blueprint.levels) {
|
|
276
|
+
const baseX = level.coordinates[0];
|
|
277
|
+
const baseY = level.coordinates[1];
|
|
278
|
+
const baseZ = level.coordinates[2];
|
|
279
|
+
const placement = level.placement;
|
|
280
|
+
|
|
281
|
+
// Update bounds
|
|
282
|
+
minX = Math.min(minX, baseX);
|
|
283
|
+
maxX = Math.max(maxX, baseX + placement[0].length - 1);
|
|
284
|
+
minY = Math.min(minY, baseY);
|
|
285
|
+
maxY = Math.max(maxY, baseY);
|
|
286
|
+
minZ = Math.min(minZ, baseZ);
|
|
287
|
+
maxZ = Math.max(maxZ, baseZ + placement.length - 1);
|
|
288
|
+
|
|
289
|
+
// Loop through the 2D placement array
|
|
290
|
+
for (let z = 0; z < placement.length; z++) {
|
|
291
|
+
for (let x = 0; x < placement[z].length; x++) {
|
|
292
|
+
const blockType = placement[z][x];
|
|
293
|
+
if (blockType) {
|
|
294
|
+
const setblockCommand = `/setblock ${baseX + x} ${baseY} ${baseZ + z} air`;
|
|
295
|
+
commands.push(setblockCommand);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Calculate a position nearby the blueprint but not in it
|
|
302
|
+
const nearbyPosition = {
|
|
303
|
+
x: maxX + 5, // Move 5 blocks to the right
|
|
304
|
+
y: minY, // Stay on the lowest level of the blueprint
|
|
305
|
+
z: minZ // Stay aligned with the front of the blueprint
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
return { commands, nearbyPosition };
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Systematically builds the houses by placing them next to the already existing rooms. Still uses randomness for what gets placed next.
|
|
315
|
+
* @param m width of the 3D space
|
|
316
|
+
* @param n height of the 3D space
|
|
317
|
+
* @param p depth of the 3D space
|
|
318
|
+
* @param rooms Number of rooms to attempt to generate
|
|
319
|
+
* @param minRoomWidth
|
|
320
|
+
* @param minRoomLength
|
|
321
|
+
* @param minRoomDepth
|
|
322
|
+
* @param roomVariance How much the room size will vary
|
|
323
|
+
* @param wrapping material of wrapping (air, glass, etc...) -> default is air
|
|
324
|
+
* @param carpetStyle 0,1,2 increasingly more complex
|
|
325
|
+
* @param windowStyle 0,1,2 increasingly more complex
|
|
326
|
+
* @param complexity 0,1,2,3,4 for increasingly complex materials for room generation
|
|
327
|
+
* @param startCoord an array of the x,y,z coordinates to create the blueprint. default = [148,-60,-170]
|
|
328
|
+
* @returns a blueprint object
|
|
329
|
+
*/
|
|
330
|
+
export function proceduralGeneration(m = 20,
|
|
331
|
+
n = 20,
|
|
332
|
+
p = 20,
|
|
333
|
+
rooms = 8,
|
|
334
|
+
minRoomWidth = 5,
|
|
335
|
+
minRoomLength = 5,
|
|
336
|
+
minRoomDepth = 6,
|
|
337
|
+
roomVariance = 5,
|
|
338
|
+
wrapping = "air",
|
|
339
|
+
carpetStyle = 1,
|
|
340
|
+
windowStyle = 1,
|
|
341
|
+
complexity = 4,
|
|
342
|
+
startCoord = [148,-60,-170]) {
|
|
343
|
+
// Build 3D space
|
|
344
|
+
const matrix = Array.from({length: p}, () =>
|
|
345
|
+
Array.from({length: m}, () =>
|
|
346
|
+
Array(n).fill('air')
|
|
347
|
+
)
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
// todo: extrapolate into another param? then have set materials be dynamic?
|
|
351
|
+
let roomMaterials = ["stone", "terracotta", "quartz_block", "copper_block", "purpur_block"]
|
|
352
|
+
|
|
353
|
+
if (complexity < roomMaterials.length) {
|
|
354
|
+
roomMaterials = roomMaterials.slice(0, complexity + 1);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Mark entire outer border with 'stone'
|
|
358
|
+
for (let z = 0; z < p; z++) {
|
|
359
|
+
for (let x = 0; x < m; x++) {
|
|
360
|
+
for (let y = 0; y < n; y++) {
|
|
361
|
+
if (
|
|
362
|
+
z === 0 || z === p - 1 || // Top and bottom faces
|
|
363
|
+
x === 0 || x === m - 1 || // Front and back faces
|
|
364
|
+
y === 0 || y === n - 1 // Left and right faces
|
|
365
|
+
) {
|
|
366
|
+
matrix[z][x][y] = 'stone';
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Replace outer layer with wrap
|
|
373
|
+
for (let z = 0; z < p; z++) {
|
|
374
|
+
for (let x = 0; x < m; x++) {
|
|
375
|
+
for (let y = 0; y < n; y++) {
|
|
376
|
+
if (
|
|
377
|
+
(z === p - 1 || // Top face
|
|
378
|
+
x === 0 || x === m - 1 || // Front and back faces
|
|
379
|
+
y === 0 || y === n - 1) // Left and right faces
|
|
380
|
+
) {
|
|
381
|
+
matrix[z][x][y] = wrapping;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
let placedRooms = 0;
|
|
388
|
+
let lastRoom = null;
|
|
389
|
+
|
|
390
|
+
// Direction probabilities (e.g., 'above': 40%, 'left': 15%, etc.)
|
|
391
|
+
const directionChances = [
|
|
392
|
+
{direction: 'above', chance: 0.15},
|
|
393
|
+
{direction: 'left', chance: 0.15},
|
|
394
|
+
{direction: 'right', chance: 0.15},
|
|
395
|
+
{direction: 'forward', chance: 0.15},
|
|
396
|
+
{direction: 'backward', chance: 0.15},
|
|
397
|
+
];
|
|
398
|
+
|
|
399
|
+
// Function to pick a random direction based on percentages
|
|
400
|
+
function getRandomDirection() {
|
|
401
|
+
const rand = Math.random();
|
|
402
|
+
let cumulative = 0;
|
|
403
|
+
|
|
404
|
+
for (const {direction, chance} of directionChances) {
|
|
405
|
+
cumulative += chance;
|
|
406
|
+
if (rand <= cumulative) return direction;
|
|
407
|
+
}
|
|
408
|
+
return directionChances[1].direction; // Fallback to the first direction
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Ensures no rooms overlap except at edges
|
|
412
|
+
function isSpaceValid(newX, newY, newZ, newLength, newWidth, newDepth) {
|
|
413
|
+
for (let di = 0; di < newDepth; di++) {
|
|
414
|
+
for (let dj = 0; dj < newLength; dj++) {
|
|
415
|
+
for (let dk = 0; dk < newWidth; dk++) {
|
|
416
|
+
const x = newX + dj;
|
|
417
|
+
const y = newY + dk;
|
|
418
|
+
const z = newZ + di;
|
|
419
|
+
|
|
420
|
+
// Skip checking the outermost borders of the new room (these can overlap with stone)
|
|
421
|
+
if (dj === 0 || dj === newLength - 1 ||
|
|
422
|
+
dk === 0 || dk === newWidth - 1 ||
|
|
423
|
+
di === 0 || di === newDepth - 1) {
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// For non-border spaces, ensure they're air
|
|
428
|
+
if (matrix[z][x][y] !== 'air') {
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return true;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function validateAndBuildBorder(matrix, newX, newY, newZ, newLength, newWidth, newDepth, m, n, p, material) {
|
|
438
|
+
// Allow rooms to use the matrix edges (note the <= instead of <)
|
|
439
|
+
if (
|
|
440
|
+
newX >= 0 && newX + newLength <= m &&
|
|
441
|
+
newY >= 0 && newY + newWidth <= n &&
|
|
442
|
+
newZ >= 0 && newZ + newDepth <= p &&
|
|
443
|
+
isSpaceValid(newX, newY, newZ, newLength, newWidth, newDepth)
|
|
444
|
+
) {
|
|
445
|
+
// console.log(`Placing room at (${newX}, ${newY}, ${newZ}) with dimensions (${newLength}x${newWidth}x${newDepth})`);
|
|
446
|
+
for (let di = 0; di < newDepth; di++) {
|
|
447
|
+
for (let dj = 0; dj < newLength; dj++) {
|
|
448
|
+
for (let dk = 0; dk < newWidth; dk++) {
|
|
449
|
+
const x = newX + dj;
|
|
450
|
+
const y = newY + dk;
|
|
451
|
+
const z = newZ + di;
|
|
452
|
+
|
|
453
|
+
// If this is at a matrix border, don't modify it
|
|
454
|
+
if (z === 0) {
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
// if (x === 0 || x === m - 1 ||
|
|
458
|
+
// y === 0 || y === n - 1 ||
|
|
459
|
+
// z === 0 || z === p - 1) {
|
|
460
|
+
// continue;
|
|
461
|
+
// }
|
|
462
|
+
|
|
463
|
+
// For non-border spaces, check if this is a floor that should be shared
|
|
464
|
+
//was: === 'stone'
|
|
465
|
+
if (di === 0 && matrix[z - 1][x][y] !== 'air') {
|
|
466
|
+
// Skip creating floor if there's a ceiling below
|
|
467
|
+
matrix[z][x][y] = 'air';
|
|
468
|
+
} else if (di === 0 || di === newDepth - 1 ||
|
|
469
|
+
dj === 0 || dj === newLength - 1 ||
|
|
470
|
+
dk === 0 || dk === newWidth - 1) {
|
|
471
|
+
matrix[z][x][y] = material;
|
|
472
|
+
} else {
|
|
473
|
+
matrix[z][x][y] = 'air';
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return true;
|
|
481
|
+
}
|
|
482
|
+
return false;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function addDoor(matrix, x, y, z, material) {
|
|
486
|
+
matrix[z][x][y] = material;
|
|
487
|
+
|
|
488
|
+
// Place the lower half of the door
|
|
489
|
+
// matrix[z + 1][x][y] = 'dark_oak_door[half=lower, hinge=left]';
|
|
490
|
+
|
|
491
|
+
matrix[z + 1][x][y] = 'dark_oak_door';
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
// Place the upper half of the door
|
|
495
|
+
// matrix[z + 2][x][y] = 'dark_oak_door[half=upper, hinge=left]';
|
|
496
|
+
matrix[z + 2][x][y] = 'dark_oak_door';
|
|
497
|
+
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
// Takes in a room and randomly converts some faces to be windows
|
|
502
|
+
function addWindowsAsSquares(matrix, x, y, z, newLength, newWidth, newDepth, material) {
|
|
503
|
+
// Matrix dimensions
|
|
504
|
+
const matrixDepth = matrix.length;
|
|
505
|
+
const matrixLength = matrix[0].length;
|
|
506
|
+
const matrixWidth = matrix[0][0].length;
|
|
507
|
+
const windowX = Math.ceil(minRoomWidth / 2)
|
|
508
|
+
const windowY = Math.ceil(minRoomLength / 2)
|
|
509
|
+
const windowZ = Math.ceil(minRoomDepth / 2)
|
|
510
|
+
|
|
511
|
+
// Helper function to check if coordinates are within bounds
|
|
512
|
+
function isInBounds(z, x, y) {
|
|
513
|
+
return z >= 0 && z < matrixDepth &&
|
|
514
|
+
x >= 0 && x < matrixLength &&
|
|
515
|
+
y >= 0 && y < matrixWidth;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Front and back faces (z is constant)
|
|
519
|
+
if (Math.random() < 0.8) {
|
|
520
|
+
let centerX = x + Math.floor(newLength / 2 - windowX / 2);
|
|
521
|
+
let centerY = y + Math.floor(newWidth / 2 - windowY / 2);
|
|
522
|
+
|
|
523
|
+
for (let dx = 0; dx <= windowX; dx++) {
|
|
524
|
+
for (let dy = 0; dy <= windowY; dy++) {
|
|
525
|
+
let frontZ = z;
|
|
526
|
+
let backZ = z + newDepth - 1;
|
|
527
|
+
|
|
528
|
+
if (isInBounds(frontZ, centerX + dx, centerY + dy) &&
|
|
529
|
+
matrix[frontZ][centerX + dx][centerY + dy] === material) {
|
|
530
|
+
matrix[frontZ][centerX + dx][centerY + dy] = 'glass';
|
|
531
|
+
}
|
|
532
|
+
if (isInBounds(backZ, centerX + dx, centerY + dy) &&
|
|
533
|
+
matrix[backZ][centerX + dx][centerY + dy] === material) {
|
|
534
|
+
matrix[backZ][centerX + dx][centerY + dy] = 'glass';
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Left and right faces (x is constant)
|
|
541
|
+
if (Math.random() < 0.8) {
|
|
542
|
+
let centerZ = z + Math.floor(newDepth / 2 - windowZ / 2);
|
|
543
|
+
let centerY = y + Math.floor(newWidth / 2 - windowY / 2);
|
|
544
|
+
|
|
545
|
+
for (let dz = 0; dz <= windowZ; dz++) {
|
|
546
|
+
for (let dy = 0; dy <= windowY; dy++) {
|
|
547
|
+
let leftX = x;
|
|
548
|
+
let rightX = x + newLength - 1;
|
|
549
|
+
|
|
550
|
+
if (isInBounds(centerZ + dz, leftX, centerY + dy) &&
|
|
551
|
+
matrix[centerZ + dz][leftX][centerY + dy] === material) {
|
|
552
|
+
matrix[centerZ + dz][leftX][centerY + dy] = 'glass';
|
|
553
|
+
}
|
|
554
|
+
if (isInBounds(centerZ + dz, rightX, centerY + dy) &&
|
|
555
|
+
matrix[centerZ + dz][rightX][centerY + dy] === material) {
|
|
556
|
+
matrix[centerZ + dz][rightX][centerY + dy] = 'glass';
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Top and bottom faces (y is constant)
|
|
563
|
+
if (Math.random() < 0.8) {
|
|
564
|
+
let centerX = x + Math.floor(newLength / 2 - windowX / 2);
|
|
565
|
+
let centerZ = z + Math.floor(newDepth / 2 - windowZ / 2);
|
|
566
|
+
|
|
567
|
+
for (let dx = 0; dx <= windowX; dx++) {
|
|
568
|
+
for (let dz = 0; dz <= windowZ; dz++) {
|
|
569
|
+
let bottomY = y;
|
|
570
|
+
let topY = y + newWidth - 1;
|
|
571
|
+
|
|
572
|
+
if (isInBounds(centerZ + dz, centerX + dx, bottomY) &&
|
|
573
|
+
matrix[centerZ + dz][centerX + dx][bottomY] === material) {
|
|
574
|
+
matrix[centerZ + dz][centerX + dx][bottomY] = 'glass';
|
|
575
|
+
}
|
|
576
|
+
if (isInBounds(centerZ + dz, centerX + dx, topY) &&
|
|
577
|
+
matrix[centerZ + dz][centerX + dx][topY] === material) {
|
|
578
|
+
matrix[centerZ + dz][centerX + dx][topY] = 'glass';
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function addWindowsAsPlane(matrix, x, y, z, newLength, newWidth, newDepth, material) {
|
|
586
|
+
// Ensure the new dimensions are within bounds
|
|
587
|
+
const maxX = matrix[0].length;
|
|
588
|
+
const maxY = matrix[0][0].length;
|
|
589
|
+
const maxZ = matrix.length;
|
|
590
|
+
|
|
591
|
+
// Each face has a 30% chance of becoming a window
|
|
592
|
+
if (Math.random() < 0.8) {
|
|
593
|
+
for (let dx = 0; dx < newLength; dx++) {
|
|
594
|
+
for (let dy = 0; dy < newWidth; dy++) {
|
|
595
|
+
let frontZ = z;
|
|
596
|
+
let backZ = z + newDepth - 1;
|
|
597
|
+
|
|
598
|
+
// Check bounds before modifying the matrix
|
|
599
|
+
if (frontZ >= 0 && frontZ < maxZ && x + dx >= 0 && x + dx < maxX && y + dy >= 0 && y + dy < maxY) {
|
|
600
|
+
if (matrix[frontZ][x + dx][y + dy] === material) {
|
|
601
|
+
matrix[frontZ][x + dx][y + dy] = 'glass';
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
if (backZ >= 0 && backZ < maxZ && x + dx >= 0 && x + dx < maxX && y + dy >= 0 && y + dy < maxY) {
|
|
605
|
+
if (matrix[backZ][x + dx][y + dy] === material) {
|
|
606
|
+
matrix[backZ][x + dx][y + dy] = 'glass';
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
if (Math.random() < 0.8) {
|
|
614
|
+
for (let dz = 0; dz < newDepth; dz++) {
|
|
615
|
+
for (let dy = 0; dy < newWidth; dy++) {
|
|
616
|
+
let leftX = x;
|
|
617
|
+
let rightX = x + newLength - 1;
|
|
618
|
+
|
|
619
|
+
// Check bounds before modifying the matrix
|
|
620
|
+
if (leftX >= 0 && leftX < maxX && z + dz >= 0 && z + dz < maxZ && y + dy >= 0 && y + dy < maxY) {
|
|
621
|
+
if (matrix[z + dz][leftX][y + dy] === material) {
|
|
622
|
+
matrix[z + dz][leftX][y + dy] = 'glass';
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
if (rightX >= 0 && rightX < maxX && z + dz >= 0 && z + dz < maxZ && y + dy >= 0 && y + dy < maxY) {
|
|
626
|
+
if (matrix[z + dz][rightX][y + dy] === material) {
|
|
627
|
+
matrix[z + dz][rightX][y + dy] = 'glass';
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
//still a little buggy
|
|
637
|
+
function addStairs(matrix, x, y, z, length, width, material) {
|
|
638
|
+
let currentZ = z;
|
|
639
|
+
let currentX = x + 1;
|
|
640
|
+
let currentY = y + 1;
|
|
641
|
+
let direction = 0;
|
|
642
|
+
let stepCount = 0;
|
|
643
|
+
const maxSteps = length * width; // Safety limit
|
|
644
|
+
|
|
645
|
+
while (currentZ >= 0 && currentX < x + length - 1 && currentY < y + width - 1 && stepCount < maxSteps) {
|
|
646
|
+
// Place stair block
|
|
647
|
+
matrix[currentZ][currentX][currentY] = material || 'stone';
|
|
648
|
+
|
|
649
|
+
// Clear 3 blocks above for headroom
|
|
650
|
+
for (let i = 1; i <= 3; i++) {
|
|
651
|
+
if (currentZ + i < matrix.length) {
|
|
652
|
+
matrix[currentZ + i][currentX][currentY] = 'air';
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Move to next position based on direction
|
|
657
|
+
if (direction === 0) {
|
|
658
|
+
currentX++;
|
|
659
|
+
if (currentX >= x + length - 1) {
|
|
660
|
+
currentX = x + length - 2;
|
|
661
|
+
direction = 1;
|
|
662
|
+
} else {
|
|
663
|
+
currentZ--;
|
|
664
|
+
}
|
|
665
|
+
} else {
|
|
666
|
+
currentY++;
|
|
667
|
+
if (currentY >= y + width - 1) {
|
|
668
|
+
currentY = y + width - 2;
|
|
669
|
+
direction = 0;
|
|
670
|
+
} else {
|
|
671
|
+
currentZ--;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
stepCount++;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
function addCarpet(probability, matrix, newX, newY, newZ, newLength, newWidth, material) {
|
|
680
|
+
let colors = ["blue", "cyan", "light_blue", "lime"];
|
|
681
|
+
|
|
682
|
+
// Iterate through the dimensions of the room
|
|
683
|
+
for (let dx = 1; dx < newLength - 1; dx++) {
|
|
684
|
+
for (let dy = 1; dy < newWidth - 1; dy++) {
|
|
685
|
+
let x = newX + dx;
|
|
686
|
+
let y = newY + dy;
|
|
687
|
+
let z = newZ; // Start at floor level
|
|
688
|
+
|
|
689
|
+
// Check if there is floor (not air)
|
|
690
|
+
if (matrix[z][x][y] === material) {
|
|
691
|
+
// Consider a random probability of adding a carpet
|
|
692
|
+
if (Math.random() < probability) {
|
|
693
|
+
// Choose a random color for the carpet
|
|
694
|
+
let randomColor = colors[Math.floor(Math.random() * colors.length)];
|
|
695
|
+
// Add carpet one z position above the floor with a random color
|
|
696
|
+
matrix[z + 1][x][y] = `${randomColor}_carpet`;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function addLadder(matrix, x, y, z) {
|
|
704
|
+
let currentZ = z + 1;
|
|
705
|
+
|
|
706
|
+
// turn the floor into air where person would go up
|
|
707
|
+
matrix[currentZ][x + 1][y] = 'air';
|
|
708
|
+
|
|
709
|
+
// Build the first 3 ladder segments from floor level downwards
|
|
710
|
+
for (let i = 0; i < 3; i++) {
|
|
711
|
+
// Place stone block behind ladder
|
|
712
|
+
matrix[currentZ][x - 1][y] = 'stone';
|
|
713
|
+
// Place ladder
|
|
714
|
+
matrix[currentZ][x][y] = 'ladder[facing=north]';
|
|
715
|
+
currentZ -= 1;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// Continue building ladder downwards until a floor is hit or we reach the bottom
|
|
719
|
+
while (currentZ >= 0 && matrix[currentZ][x][y] === 'air') {
|
|
720
|
+
// Place stone block behind ladder
|
|
721
|
+
matrix[currentZ][x - 1][y] = 'stone';
|
|
722
|
+
// Place ladder
|
|
723
|
+
matrix[currentZ][x][y] = 'ladder[facing=north]';
|
|
724
|
+
|
|
725
|
+
// Move down
|
|
726
|
+
currentZ--;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
function embellishments(carpet, windowStyle, matrix, newX, newY, newZ, newLength, newWidth, newDepth, material) {
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
switch (windowStyle) {
|
|
735
|
+
case 0:
|
|
736
|
+
break;
|
|
737
|
+
case 1:
|
|
738
|
+
addWindowsAsSquares(matrix, newZ, newY, newZ, newLength, newWidth, newDepth, material)
|
|
739
|
+
break;
|
|
740
|
+
case 2:
|
|
741
|
+
addWindowsAsPlane(matrix, newZ, newY, newZ, newLength, newWidth, newDepth, material)
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
switch (carpet) {
|
|
746
|
+
case 0:
|
|
747
|
+
break;
|
|
748
|
+
case 1:
|
|
749
|
+
addCarpet(0.3, matrix, newX, newY, newZ, newLength, newWidth, material);
|
|
750
|
+
break;
|
|
751
|
+
case 2:
|
|
752
|
+
addCarpet(0.7, matrix, newX, newY, newZ, newLength, newWidth, material)
|
|
753
|
+
break;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
// Places rooms until we can't, or we place all
|
|
761
|
+
// attempts random configurations of rooms in random directions.
|
|
762
|
+
while (placedRooms < rooms) {
|
|
763
|
+
let roomPlaced = false;
|
|
764
|
+
|
|
765
|
+
for (let attempt = 0; attempt < 150; attempt++) {
|
|
766
|
+
|
|
767
|
+
const material = roomMaterials[Math.floor(Math.random() * roomMaterials.length)];
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
// dimensions of room
|
|
771
|
+
const newLength = Math.max(minRoomLength, Math.floor(Math.random() * roomVariance) + minRoomLength);
|
|
772
|
+
const newWidth = Math.max(minRoomWidth, Math.floor(Math.random() * roomVariance) + minRoomWidth);
|
|
773
|
+
const newDepth = Math.max(minRoomDepth, Math.floor(Math.random() * Math.floor(roomVariance / 2)) + minRoomDepth);
|
|
774
|
+
let newX, newY, newZ;
|
|
775
|
+
|
|
776
|
+
// first room is special
|
|
777
|
+
if (placedRooms === 0) {
|
|
778
|
+
// First room placement
|
|
779
|
+
newX = Math.floor(Math.random() * (m - newLength - 1)) + 1;
|
|
780
|
+
newY = Math.floor(Math.random() * (n - newWidth - 1)) + 1;
|
|
781
|
+
newZ = 0; // Ground floor
|
|
782
|
+
|
|
783
|
+
if (validateAndBuildBorder(matrix, newX, newY, newZ, newLength, newWidth, newDepth, m, n, p, material)) {
|
|
784
|
+
lastRoom = {x: newX, y: newY, z: newZ, length: newLength, width: newWidth, depth: newDepth};
|
|
785
|
+
roomPlaced = true;
|
|
786
|
+
placedRooms++;
|
|
787
|
+
|
|
788
|
+
// Add doors to all four sides
|
|
789
|
+
// Left side
|
|
790
|
+
addDoor(matrix, newX, newY + Math.floor(newWidth / 2), newZ, material);
|
|
791
|
+
// Right side
|
|
792
|
+
addDoor(matrix, newX + newLength - 1, newY + Math.floor(newWidth / 2), newZ, material);
|
|
793
|
+
// Front side
|
|
794
|
+
addDoor(matrix, newX + Math.floor(newLength / 2), newY, newZ, material);
|
|
795
|
+
// Back side
|
|
796
|
+
addDoor(matrix, newX + Math.floor(newLength / 2), newY + newWidth - 1, newZ, material);
|
|
797
|
+
|
|
798
|
+
addCarpet(0.7, matrix, newX, newY, newZ, newLength, newWidth)
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
break;
|
|
802
|
+
} else {
|
|
803
|
+
const direction = getRandomDirection();
|
|
804
|
+
|
|
805
|
+
switch (direction) {
|
|
806
|
+
case 'above':
|
|
807
|
+
newX = lastRoom.x;
|
|
808
|
+
newY = lastRoom.y;
|
|
809
|
+
newZ = lastRoom.z + lastRoom.depth - 1;
|
|
810
|
+
if (validateAndBuildBorder(matrix, newX, newY, newZ, newLength, newWidth, newDepth, m, n, p, material)) {
|
|
811
|
+
|
|
812
|
+
embellishments(carpetStyle, windowStyle, matrix, newX, newY, newZ, newLength, newWidth, newDepth, material)
|
|
813
|
+
|
|
814
|
+
// addLadder(matrix, lastRoom.x + Math.floor(lastRoom.length / 2),
|
|
815
|
+
// lastRoom.y + Math.floor(lastRoom.width / 2),
|
|
816
|
+
// newZ); // Adding the ladder
|
|
817
|
+
|
|
818
|
+
addStairs(matrix, newX, newY, newZ, newLength, newWidth, material)
|
|
819
|
+
|
|
820
|
+
|
|
821
|
+
lastRoom = {x: newX, y: newY, z: newZ, length: newLength, width: newWidth, depth: newDepth};
|
|
822
|
+
roomPlaced = true;
|
|
823
|
+
placedRooms++;
|
|
824
|
+
break;
|
|
825
|
+
}
|
|
826
|
+
break;
|
|
827
|
+
|
|
828
|
+
case 'left':
|
|
829
|
+
newX = lastRoom.x - newLength + 1;
|
|
830
|
+
newY = lastRoom.y;
|
|
831
|
+
newZ = lastRoom.z;
|
|
832
|
+
if (validateAndBuildBorder(matrix, newX, newY, newZ, newLength, newWidth, newDepth, m, n, p, material)) {
|
|
833
|
+
|
|
834
|
+
|
|
835
|
+
embellishments(carpetStyle, windowStyle, matrix, newX, newY, newZ, newLength, newWidth, newDepth, material)
|
|
836
|
+
|
|
837
|
+
|
|
838
|
+
addDoor(matrix, lastRoom.x, lastRoom.y + Math.floor(lastRoom.width / 2), lastRoom.z, material);
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
lastRoom = {x: newX, y: newY, z: newZ, length: newLength, width: newWidth, depth: newDepth};
|
|
842
|
+
roomPlaced = true;
|
|
843
|
+
placedRooms++;
|
|
844
|
+
break;
|
|
845
|
+
}
|
|
846
|
+
break;
|
|
847
|
+
|
|
848
|
+
case 'right':
|
|
849
|
+
newX = lastRoom.x + lastRoom.length - 1;
|
|
850
|
+
newY = lastRoom.y;
|
|
851
|
+
newZ = lastRoom.z;
|
|
852
|
+
if (validateAndBuildBorder(matrix, newX, newY, newZ, newLength, newWidth, newDepth, m, n, p, material)) {
|
|
853
|
+
|
|
854
|
+
embellishments(carpetStyle, windowStyle, matrix, newX, newY, newZ, newLength, newWidth, newDepth, material)
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
addDoor(matrix, lastRoom.x + lastRoom.length - 1,
|
|
858
|
+
lastRoom.y + Math.floor(lastRoom.width / 2),
|
|
859
|
+
lastRoom.z, material);
|
|
860
|
+
|
|
861
|
+
|
|
862
|
+
lastRoom = {x: newX, y: newY, z: newZ, length: newLength, width: newWidth, depth: newDepth};
|
|
863
|
+
roomPlaced = true;
|
|
864
|
+
placedRooms++;
|
|
865
|
+
break;
|
|
866
|
+
}
|
|
867
|
+
break;
|
|
868
|
+
|
|
869
|
+
case 'forward':
|
|
870
|
+
newX = lastRoom.x;
|
|
871
|
+
newY = lastRoom.y + lastRoom.width - 1;
|
|
872
|
+
newZ = lastRoom.z;
|
|
873
|
+
if (validateAndBuildBorder(matrix, newX, newY, newZ, newLength, newWidth, newDepth, m, n, p, material)) {
|
|
874
|
+
|
|
875
|
+
embellishments(carpetStyle, windowStyle, matrix, newX, newY, newZ, newLength, newWidth, newDepth, material)
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
addDoor(matrix, lastRoom.x + Math.floor(lastRoom.length / 2),
|
|
879
|
+
lastRoom.y + lastRoom.width - 1,
|
|
880
|
+
lastRoom.z, material);
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
lastRoom = {x: newX, y: newY, z: newZ, length: newLength, width: newWidth, depth: newDepth};
|
|
884
|
+
roomPlaced = true;
|
|
885
|
+
placedRooms++;
|
|
886
|
+
break;
|
|
887
|
+
}
|
|
888
|
+
break;
|
|
889
|
+
|
|
890
|
+
case 'backward':
|
|
891
|
+
newX = lastRoom.x;
|
|
892
|
+
newY = lastRoom.y - newWidth + 1;
|
|
893
|
+
newZ = lastRoom.z;
|
|
894
|
+
if (validateAndBuildBorder(matrix, newX, newY, newZ, newLength, newWidth, newDepth, m, n, p, material)) {
|
|
895
|
+
|
|
896
|
+
embellishments(carpetStyle, windowStyle, matrix, newX, newY, newZ, newLength, newWidth, newDepth, material)
|
|
897
|
+
|
|
898
|
+
|
|
899
|
+
addDoor(matrix, lastRoom.x + Math.floor(lastRoom.length / 2),
|
|
900
|
+
lastRoom.y,
|
|
901
|
+
lastRoom.z, material);
|
|
902
|
+
|
|
903
|
+
|
|
904
|
+
lastRoom = {x: newX, y: newY, z: newZ, length: newLength, width: newWidth, depth: newDepth};
|
|
905
|
+
roomPlaced = true;
|
|
906
|
+
placedRooms++;
|
|
907
|
+
break;
|
|
908
|
+
}
|
|
909
|
+
break;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
if (roomPlaced) {
|
|
913
|
+
break;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
if (!roomPlaced) {
|
|
919
|
+
console.warn(`Could not place room ${placedRooms + 1}`);
|
|
920
|
+
break;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// uncomment to visualize blueprint output
|
|
925
|
+
// printMatrix(matrix)
|
|
926
|
+
|
|
927
|
+
return matrixToBlueprint(matrix, startCoord)
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
|
|
931
|
+
|
|
932
|
+
|
|
933
|
+
/**
|
|
934
|
+
* for cutesy output
|
|
935
|
+
* @param matrix
|
|
936
|
+
*/
|
|
937
|
+
function printMatrix(matrix) {
|
|
938
|
+
matrix.forEach((layer, layerIndex) => {
|
|
939
|
+
console.log(`Layer ${layerIndex}:`);
|
|
940
|
+
layer.forEach(row => {
|
|
941
|
+
console.log(
|
|
942
|
+
row.map(cell => {
|
|
943
|
+
switch (cell) {
|
|
944
|
+
case 'stone': return '█'; // Wall
|
|
945
|
+
case 'air': return '.'; // Open space
|
|
946
|
+
case 'dark_oak_door[half=upper, hinge=left]': return 'D';
|
|
947
|
+
case 'dark_oak_door[half=lower, hinge=left]': return 'D';
|
|
948
|
+
case 'oak_stairs[facing=north]': return 'S'; // Stairs
|
|
949
|
+
case 'oak_stairs[facing=east]': return 'S'; // Stairs
|
|
950
|
+
case 'oak_stairs[facing=south]': return 'S'; // Stairs
|
|
951
|
+
case 'oak_stairs[facing=west]': return 'S'; // Stairs
|
|
952
|
+
case 'glass': return 'W'
|
|
953
|
+
|
|
954
|
+
|
|
955
|
+
default: return '?'; // Unknown or unmarked space
|
|
956
|
+
}
|
|
957
|
+
}).join(' ')
|
|
958
|
+
);
|
|
959
|
+
});
|
|
960
|
+
console.log('---');
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
/**
|
|
965
|
+
* Converts a 3D matrix into a Minecraft blueprint format
|
|
966
|
+
* @param {Array<Array<Array<string>>>} matrix - 3D matrix of block types
|
|
967
|
+
* @param {number[]} startCoord - Starting coordinates [x, y, z]
|
|
968
|
+
* @returns {Object} a Blueprint object in Minecraft format
|
|
969
|
+
*/
|
|
970
|
+
function matrixToBlueprint(matrix, startCoord) {
|
|
971
|
+
// Validate inputs
|
|
972
|
+
if (!Array.isArray(matrix) || !Array.isArray(startCoord) || startCoord.length !== 3) {
|
|
973
|
+
console.log(matrix)
|
|
974
|
+
throw new Error('Invalid input format');
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
const [startX, startY, startZ] = startCoord;
|
|
978
|
+
|
|
979
|
+
|
|
980
|
+
// CONSIDER: using blueprint class here?
|
|
981
|
+
return {
|
|
982
|
+
levels: matrix.map((level, levelIndex) => ({
|
|
983
|
+
level: levelIndex,
|
|
984
|
+
coordinates: [
|
|
985
|
+
startX,
|
|
986
|
+
startY + levelIndex,
|
|
987
|
+
startZ
|
|
988
|
+
],
|
|
989
|
+
placement: level.map(row =>
|
|
990
|
+
// Ensure each block is a string, default to 'air' if undefined
|
|
991
|
+
row.map(block => block?.toString() || 'air')
|
|
992
|
+
)
|
|
993
|
+
}))
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
async function getBlockName(bot, coordinate) {
|
|
998
|
+
const blockAtLocation = bot.blockAt(new Vec3(coordinate.x, coordinate.y, coordinate.z));
|
|
999
|
+
return blockAtLocation ? bot.registry.blocks[blockAtLocation.type].name : "air";
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
/**
|
|
1003
|
+
* Converts a world location to a blueprint. takes some time to ensure that the chunks are loaded before conversion.
|
|
1004
|
+
* @param startCoord - [x,y,z] that signifies the start of the blueprint
|
|
1005
|
+
* @param y_amount - how many spaces you want to register from the start coordinate in the y dimension
|
|
1006
|
+
* @param x_amount - how many spaces in the x direction on minecraft
|
|
1007
|
+
* @param z_amount - how many spaces from the start coordinate in the z direction in minecraft
|
|
1008
|
+
* @param bot - the mineflayer agent (ex. andy)
|
|
1009
|
+
* @returns - a Blueprint object of the converted blueprint
|
|
1010
|
+
*/
|
|
1011
|
+
export async function worldToBlueprint(startCoord, y_amount, x_amount, z_amount, bot) {
|
|
1012
|
+
await bot.waitForChunksToLoad();
|
|
1013
|
+
|
|
1014
|
+
const materials = {};
|
|
1015
|
+
|
|
1016
|
+
const levels = [];
|
|
1017
|
+
for (let y = 0; y < y_amount; y++) {
|
|
1018
|
+
const placement = [];
|
|
1019
|
+
const coordinates = [startCoord.x, startCoord.y + y, startCoord.z];
|
|
1020
|
+
for (let z = 0; z < z_amount; z++) {
|
|
1021
|
+
const row = [];
|
|
1022
|
+
for (let x = 0; x < x_amount; x++) {
|
|
1023
|
+
const worldCoord = {
|
|
1024
|
+
x: startCoord.x + x,
|
|
1025
|
+
y: startCoord.y + y,
|
|
1026
|
+
z: startCoord.z + z
|
|
1027
|
+
};
|
|
1028
|
+
await bot.waitForChunksToLoad(worldCoord);
|
|
1029
|
+
const blockName = await getBlockName(bot, worldCoord);
|
|
1030
|
+
row.push(blockName);
|
|
1031
|
+
if (blockName !== 'air') {
|
|
1032
|
+
materials[blockName] = (materials[blockName] || 0) + 1;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
placement.push(row);
|
|
1036
|
+
}
|
|
1037
|
+
levels.push({
|
|
1038
|
+
level: y,
|
|
1039
|
+
coordinates: coordinates,
|
|
1040
|
+
placement: placement
|
|
1041
|
+
})
|
|
1042
|
+
}
|
|
1043
|
+
console.log(levels);
|
|
1044
|
+
const blueprint_data = {
|
|
1045
|
+
materials: materials,
|
|
1046
|
+
levels: levels
|
|
1047
|
+
}
|
|
1048
|
+
return blueprint_data
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
export function blueprintToTask(blueprint_data, num_agents) {
|
|
1052
|
+
let initialInventory = {}
|
|
1053
|
+
for (let j = 0; j < num_agents; j++) {
|
|
1054
|
+
initialInventory[JSON.stringify(j)] = {"diamond_pickaxe": 1, "diamond_axe": 1, "diamond_shovel": 1};
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
let give_agent = 0;
|
|
1058
|
+
console.log("materials", blueprint_data.materials)
|
|
1059
|
+
for (const key of Object.keys(blueprint_data.materials)) {
|
|
1060
|
+
initialInventory[JSON.stringify(give_agent)][key] = blueprint_data.materials[key];
|
|
1061
|
+
give_agent = (give_agent + 1) % num_agents;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
const task = {
|
|
1065
|
+
type: "construction",
|
|
1066
|
+
goal: "Make a structure with the blueprint below",
|
|
1067
|
+
conversation: "Let's share materials and make a structure with the blueprint",
|
|
1068
|
+
agent_count: num_agents,
|
|
1069
|
+
blueprint: blueprint_data,
|
|
1070
|
+
initial_inventory: initialInventory,
|
|
1071
|
+
};
|
|
1072
|
+
return task;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
// testing code
|
|
1076
|
+
|
|
1077
|
+
// let blueprint = proceduralGeneration(20,10,20)
|
|
1078
|
+
// const b = new Blueprint(blueprint)
|
|
1079
|
+
// const result = b.autoBuild();
|
|
1080
|
+
// const commands = result.commands;
|
|
1081
|
+
// const nearbyPosition = result.nearbyPosition;
|
|
1082
|
+
//
|
|
1083
|
+
//
|
|
1084
|
+
// import {initBot} from "../../utils/mcdata.js";
|
|
1085
|
+
// let bot = initBot("andy");
|
|
1086
|
+
|
|
1087
|
+
|
|
1088
|
+
// example usage of world->blueprint function
|
|
1089
|
+
|
|
1090
|
+
// bot.once('spawn', async () => {
|
|
1091
|
+
// console.log("nearby position", nearbyPosition);
|
|
1092
|
+
// bot.chat(`/tp @andy ${nearbyPosition.x} ${nearbyPosition.y} ${nearbyPosition.z}`);
|
|
1093
|
+
// for (const command of commands) {
|
|
1094
|
+
// bot.chat(command);
|
|
1095
|
+
// }
|
|
1096
|
+
// const startCoord = {
|
|
1097
|
+
// x: 148,
|
|
1098
|
+
// y: -60,
|
|
1099
|
+
// z: -170
|
|
1100
|
+
// };
|
|
1101
|
+
// // [148,-60,-170] is default start for procedural generation
|
|
1102
|
+
//
|
|
1103
|
+
// const worldOutput = await worldToBlueprint(startCoord, 20,10,20, bot)
|
|
1104
|
+
// });
|