Skip to content

Commit 655e405

Browse files
committed
Add chat function
1 parent 007dbbe commit 655e405

File tree

3 files changed

+375
-1
lines changed

3 files changed

+375
-1
lines changed

docs/clientcommands.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ declare function $(command: string): number | Array<Entity>;
2121
*/
2222
declare function print(x: string): void;
2323

24+
/**
25+
* Sends a message to the game chat, prefix with "/" to run a server-side command.
26+
* @param msg The message to send
27+
*/
28+
declare function chat(msg: string): void;
29+
2430
/**
2531
* Allows the game to run a tick. Pauses script execution until the next tick
2632
*/
@@ -353,7 +359,8 @@ interface InventoryClickOptions {
353359
*/
354360
type?: string;
355361
/**
356-
* Whether to simulate a right click rather than a left click (which is the default)
362+
* Whether to simulate a right click rather than a left click (which is the default).
363+
* When {@link type} is <tt>"throw"</tt>, then setting this to <tt>true</tt> throws all items rather than just one.
357364
*/
358365
rightClick?: boolean;
359366
/**
Lines changed: 366 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,366 @@
1+
2+
var GROUP_NAME = "rsf";
3+
var PLATFORM_Y = 64;
4+
var PLATFORM_X_MIN = 0;
5+
var PLATFORM_X_MAX = 64;
6+
var PLATFORM_Z_MIN = 0;
7+
var PLATFORM_Z_MAX = 64;
8+
var STICK_ITEM = "minecraft:stick";
9+
var FUEL_ITEM = "minecraft:charcoal";
10+
var STONE_BLOCK = "minecraft:stone";
11+
var SLAB_BLOCK = "minecraft:stone_slab";
12+
var COBBLESTONE_BLOCK = "minecraft:cobblestone";
13+
14+
// The "replenish area" is the coordinates of the furnace block
15+
var replenishArea;
16+
var craftingTable;
17+
var smeltingChest;
18+
19+
var directionNames = ["north", "east", "south", "west"];
20+
var dx = [0, 1, 0, -1];
21+
var dz = [-1, 0, 1, 0];
22+
var opposite = function(dir) { return (dir + 2) % 4; };
23+
24+
var isChestContainer = function(type) { return type === "generic_9x3" || type === "generic_9x6"; };
25+
26+
var openContainer = function(x, y, z, type) {
27+
if (typeof(type) === "string") {
28+
var typeName = type;
29+
type = function(it) { return it === typeName };
30+
}
31+
if (!player.rightClick(x, y, z))
32+
throw new Error("Could not right click on container at " + x + ", " + y + ", " + z);
33+
var timeout = 0;
34+
while (player.openContainer === null || !type(player.openContainer.type)) {
35+
tick();
36+
timeout++;
37+
if (timeout > 100)
38+
throw new Error("Failed to open container at " + x + ", " + y + ", " + z);
39+
}
40+
};
41+
42+
var anticheatDelay = function() {
43+
for (var i = 0; i < 20; i++)
44+
tick();
45+
};
46+
47+
var findReplenishArea = function() {
48+
var minDistanceSq = 1000000;
49+
for (var xDelta = -7; xDelta <= 7; xDelta++) {
50+
for (var zDelta = -7; zDelta <= 7; zDelta++) {
51+
for (var yDelta = -7; yDelta <= 7; yDelta++) {
52+
var distanceSq = xDelta * xDelta + yDelta * yDelta + zDelta * zDelta;
53+
if (distanceSq >= minDistanceSq)
54+
continue;
55+
var x = player.x + xDelta;
56+
var y = player.y + player.eyeHeight + yDelta;
57+
var z = player.z + zDelta;
58+
if (world.getBlock(x, y, z) === "furnace") {
59+
for (var dir = 0; dir < 4; dir++) {
60+
var craftX = x + dx[dir];
61+
var craftZ = z + dz[dir];
62+
if (world.getBlock(craftX, y, craftZ) === "crafting_table") {
63+
for (var dir2 = 0; dir2 < 4; dir2++) {
64+
var chestX = craftX + dx[dir];
65+
var chestZ = craftZ + dz[dir];
66+
if (world.getBlock(chestX, y, chestZ) === "chest" || world.getBlock(chestX, y, chestZ) === "trapped_chest") {
67+
replenishArea = [x, y, z];
68+
craftingTable = [craftX, y, craftZ];
69+
smeltingChest = [chestX, y, chestZ];
70+
minDistanceSq = distanceSq;
71+
}
72+
}
73+
}
74+
}
75+
}
76+
}
77+
}
78+
}
79+
throw new Error("Could not find replenish area");
80+
};
81+
82+
var gatherCobblestone = function(cobblestoneNeeded) {
83+
for (var xDelta = -5; xDelta <= 5; xDelta++) {
84+
for (var zDelta = -5; zDelta <= 5; zDelta++) {
85+
for (var yDelta = -5; yDelta <= 5; yDelta++) {
86+
var x = player.x + xDelta;
87+
var y = player.y + player.eyeHeight + yDelta;
88+
var z = player.z + zDelta;
89+
if (world.getBlock(x, y, z) === "chest" || world.getBlock(x, y, z) === "trapped_chest") {
90+
var blockX = Math.floor(x);
91+
var blockY = Math.floor(y);
92+
var blockZ = Math.floor(z);
93+
if (blockX === smeltingChest[0] && blockY === smeltingChest[1] && blockZ === smeltingChest[2])
94+
continue;
95+
if (Math.abs(blockX - smeltingChest[0]) + Math.abs(blockZ - smeltingChest[2]) === 1 && blockY === smeltingChest[1]
96+
&& world.getBlock(x, y, z) === world.getBlock(smeltingChest[0], smeltingChest[1], smeltingChest[2]))
97+
continue;
98+
try {
99+
openContainer(x, y, z, isChestContainer);
100+
var chestItems = player.openContainer.items;
101+
for (var i = 0; i < chestItems.length; i++) {
102+
if (chestItems[i].id === COBBLESTONE_BLOCK) {
103+
cobblestoneNeeded -= chestItems[i].Count;
104+
player.openContainer.click(i, {type: "quick_move"});
105+
if (cobblestoneNeeded <= 0)
106+
return;
107+
}
108+
}
109+
player.closeContainer();
110+
anticheatDelay();
111+
} catch (e) {
112+
if (!(e instanceof Error))
113+
throw e;
114+
}
115+
}
116+
}
117+
}
118+
}
119+
throw new Error("Not enough cobblestone");
120+
};
121+
122+
var ensureResources = function() {
123+
// Check if we already have the resources
124+
var foundStone = 0, foundSlabs = 0, foundFuel = 0;
125+
var items = player.inventory.items;
126+
for (var slot = 0; slot < 36; slot++) {
127+
if (items[slot].id === STONE_BLOCK)
128+
foundStone += items[slot].Count;
129+
else if (items[slot].id === SLAB_BLOCK)
130+
foundSlabs += items[slot].Count;
131+
else if (items[slot].id === FUEL_ITEM)
132+
foundFuel += items[slot].Count;
133+
}
134+
if (foundStone !== 0 && foundSlabs !== 0)
135+
return;
136+
137+
var slabsNeeded = Math.max(0, 64 * 10 - foundSlabs);
138+
slabsNeeded = Math.ceil(slabsNeeded / 6) * 6;
139+
var stoneNeeded = 64 * 10 - foundStone;
140+
stoneNeeded += slabsNeeded / 2;
141+
142+
// Travel near to the replenish area (blocks may not be visible until nearby)
143+
var xDistanceToReplenishArea = replenishArea[0] + 0.5 - player.x;
144+
var zDistanceToReplenishArea = replenishArea[2] + 0.5 - player.z;
145+
var hDistanceToReplenishArea = Math.sqrt(xDistanceToReplenishArea * xDistanceToReplenishArea + zDistanceToReplenishArea * zDistanceToReplenishArea);
146+
var targetX = replenishArea[0] + 0.5 + xDistanceToReplenishArea * 2 / hDistanceToReplenishArea;
147+
var targetZ = replenishArea[2] + 0.5 + zDistanceToReplenishArea * 2 / hDistanceToReplenishArea;
148+
149+
if (!player.moveTo(targetX, targetZ))
150+
throw new Error("Could not move to replenish area");
151+
152+
// Re-find the replenish area in case it has moved
153+
replenishArea = findReplenishArea();
154+
155+
if (stoneNeeded > 0) {
156+
// Search items in the smelting chest, and check if there is already enough
157+
openContainer(smeltingChest[0], smeltingChest[1], smeltingChest[2], isChestContainer);
158+
var chestItems = player.openContainer.items;
159+
var cobblestoneFound = 0;
160+
for (var i = 0; i < chestItems.length; i++) {
161+
if (chestItems[i].id === STONE_BLOCK) {
162+
stoneNeeded -= chestItems[i].Count;
163+
player.openContainer.click(i, {type: "quick_move"});
164+
if (stoneNeeded <= 0)
165+
break;
166+
} else if (chestItems[i].id === COBBLESTONE_BLOCK) {
167+
cobblestoneFound += chestItems[i].Count;
168+
}
169+
}
170+
171+
// Try to smelt more stone
172+
if (stoneNeeded > 0) {
173+
stoneNeeded = Math.ceil(stoneNeeded / 64) * 64;
174+
175+
// Find the fuel necessary to smelt this stone
176+
var fuelNeeded = stoneNeeded / 16 - foundFuel;
177+
if (fuelNeeded > 0) {
178+
for (var i = 0; i < chestItems.length; i++) {
179+
if (chestItems[i].id === FUEL_ITEM) {
180+
fuelNeeded -= chestItems[i].Count;
181+
player.openContainer.click(i, {type: "quick_move"});
182+
if (fuelNeeded <= 0)
183+
break;
184+
}
185+
}
186+
if (fuelNeeded > 0) {
187+
throw new Error("Not enough fuel");
188+
}
189+
}
190+
player.closeContainer();
191+
anticheatDelay();
192+
193+
// Ensure the cobblestone ingredients are in the smelting chest
194+
var cobblestoneNeeded = stoneNeeded - cobblestoneFound;
195+
if (cobblestoneNeeded > 0) {
196+
gatherCobblestone(cobblestoneNeeded);
197+
openContainer(smeltingChest[0], smeltingChest[1], smeltingChest[2], isChestContainer);
198+
items = player.inventory.items;
199+
for (var i = 0; i < 36; i++) {
200+
if (items[i].id === COBBLESTONE_BLOCK) {
201+
cobblestoneNeeded -= items[i].Count;
202+
player.inventory.click(i, {type: "quick_move"});
203+
}
204+
}
205+
if (cobblestoneNeeded > 0)
206+
throw new Error("An error occurred while trying to replenish cobblestone, did you throw some out?");
207+
player.closeContainer();
208+
anticheatDelay();
209+
}
210+
211+
// Check how much fuel is already in the furnace, and do an initial replenish
212+
openContainer(replenishArea[0], replenishArea[1], replenishArea[2], "furnace");
213+
var furnaceItems = player.openContainer.items;
214+
var fuelInSlot;
215+
if (furnaceItems[1].id === FUEL_ITEM) {
216+
fuelInSlot = furnaceItems[1].Count;
217+
} else if (furnaceItems[1].id && furnaceItems[1].id !== "minecraft:air") {
218+
fuelInSlot = 0;
219+
player.openContainer.click(1, {type: "throw", rightClick: true});
220+
} else {
221+
fuelInSlot = 0;
222+
}
223+
if (fuelInSlot < 64) {
224+
items = player.inventory.items;
225+
for (var i = 0; i < 36; i++) {
226+
if (items[i].id === FUEL_ITEM) {
227+
fuelInSlot = Math.min(64, fuelInSlot + items[i].Count);
228+
player.inventory.click(i, {type: "quick_move"});
229+
if (fuelInSlot === 64)
230+
break;
231+
}
232+
}
233+
}
234+
if (fuelInSlot === 0)
235+
throw new Error("An error occurred while trying to replenish fuel, did you throw some out?");
236+
player.closeContainer();
237+
anticheatDelay();
238+
239+
// Activate the furnace
240+
if (!player.pick(STICK_ITEM))
241+
throw new Error("Where's your stick?");
242+
if (!player.rightClick(replenishArea[0], replenishArea[1], replenishArea[2]))
243+
throw new Error("Unable to activate furnace");
244+
245+
// Wait for the smelting to finish, replenishing fuel as we go
246+
openContainer(replenishArea[0], replenishArea[1], replenishArea[2], "furnace");
247+
var fuelToConsume = stoneNeeded / 16;
248+
while (fuelToConsume > 0) {
249+
var newFuelInSlot = player.openContainer.items[1].Count;
250+
if (!newFuelInSlot) newFuelInSlot = 0;
251+
if (newFuelInSlot !== fuelInSlot) {
252+
fuelToConsume -= fuelInSlot - newFuelInSlot;
253+
fuelInSlot = newFuelInSlot;
254+
}
255+
256+
if (fuelInSlot === 0 && fuelToConsume > 0)
257+
throw new Error("The fuel... got lost?");
258+
259+
if (fuelInSlot < 64 && fuelInSlot < fuelToConsume) {
260+
items = player.inventory.items;
261+
for (var i = 0; i < 36; i++) {
262+
if (items[i].id === FUEL_ITEM) {
263+
fuelInSlot = Math.min(64, fuelInSlot + items[i].Count);
264+
player.inventory.click(i, {type: "quick_move"});
265+
break;
266+
}
267+
}
268+
}
269+
270+
tick();
271+
}
272+
player.closeContainer();
273+
anticheatDelay();
274+
275+
// Take the smelting result out the chest
276+
openContainer(smeltingChest[0], smeltingChest[1], smeltingChest[2], isChestContainer);
277+
chestItems = player.openContainer.items;
278+
for (var i = 0; i < chestItems.length; i++) {
279+
if (chestItems[i].id === STONE_BLOCK) {
280+
stoneNeeded -= chestItems[i].Count;
281+
player.openContainer.click(i, {type: "quick_move"});
282+
if (stoneNeeded <= 0)
283+
break;
284+
}
285+
}
286+
player.closeContainer();
287+
anticheatDelay();
288+
}
289+
}
290+
291+
// Craft slabs
292+
if (slabsNeeded > 0) {
293+
openContainer(craftingTable[0], craftingTable[1], craftingTable[2], "crafting");
294+
var recipeCount = slabsNeeded / 6;
295+
while (recipeCount > 0) {
296+
for (var slot = 1; slot <= 3; slot++) {
297+
var itemsNeeded = Math.min(64, recipeCount);
298+
items = player.inventory.items;
299+
for (var i = 0; i < 36; i++) {
300+
if (items[i].id === STONE_BLOCK) {
301+
var count = items[i].Count;
302+
player.inventory.click(i); // pickup the stone
303+
if (count <= itemsNeeded || itemsNeeded === 64) {
304+
// drop down all the stone
305+
itemsNeeded -= count;
306+
player.openContainer.click(slot);
307+
} else {
308+
// drop down as many stone as needed then put the rest back
309+
for (var j = 0; j < itemsNeeded; j++) {
310+
player.openContainer.click(slot, {rightClick: true});
311+
}
312+
player.inventory.click(i);
313+
itemsNeeded = 0;
314+
}
315+
}
316+
}
317+
}
318+
319+
var timeout = 0;
320+
while (player.openContainer.items[0].id !== SLAB_BLOCK) {
321+
tick();
322+
timeout++;
323+
if (timeout > 100)
324+
throw new Error("Failed to craft slabs");
325+
}
326+
327+
player.openContainer.click(0, {type: "quick_move"});
328+
329+
recipeCount -= 64;
330+
}
331+
player.closeContainer();
332+
anticheatDelay();
333+
}
334+
335+
if (!player.pick(STONE_BLOCK))
336+
throw new Error("Someone is tampering with the bot");
337+
chat("/ctf " + GROUP_NAME);
338+
};
339+
340+
var placePlatform = function() {
341+
for (var x = PLATFORM_X_MIN; x <= PLATFORM_X_MAX; x++) {
342+
var countDown = x % 2 !== PLATFORM_X_MIN % 2;
343+
for (var z = countDown ? PLATFORM_Z_MAX : PLATFORM_Z_MIN; countDown ? z >= PLATFORM_Z_MIN : z <= PLATFORM_Z_MAX; z += countDown ? -1 : 1) {
344+
ensureResources();
345+
if (!player.moveTo(x + 0.5, z + 0.5))
346+
throw new Error("Movement to " + x + ", " + z + " failed");
347+
player.pick(SLAB_BLOCK);
348+
var placementSide = -1;
349+
for (var dir = 0; dir < 4; dir++) {
350+
if (world.getBlockState(x + dx[dir], PLATFORM_Y, z + dz[dir]).solid) {
351+
placementSide = dir;
352+
break;
353+
}
354+
}
355+
if (placementSide === -1)
356+
throw new Error("Nothing to place the slab against at " + x + ", " + z);
357+
player.rightClick(x + dx[placementSide], PLATFORM_Y, z + dz[placementSide], directionNames[opposite(dir)]);
358+
}
359+
}
360+
};
361+
362+
replenishArea = findReplenishArea();
363+
if (player.pick(STONE_BLOCK)) {
364+
chat("/ctf " + GROUP_NAME);
365+
}
366+
placePlatform();

src/main/java/net/earthcomputer/clientcommands/script/ScriptBuiltins.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ static void addBuiltinVariables(ScriptEngine engine) {
5858
return ClientCommandManager.executeCommand(reader, command);
5959
});
6060
engine.put("print", (Consumer<String>) message -> MinecraftClient.getInstance().inGameHud.getChatHud().addMessage(new LiteralText(message)));
61+
engine.put("chat", (Consumer<String>) message -> MinecraftClient.getInstance().player.sendChatMessage(message));
6162
engine.put("tick", (Runnable) ScriptManager::passTick);
6263

6364
engine.put("Thread", new AbstractJSObject() {

0 commit comments

Comments
 (0)