Skip to content

Commit f673185

Browse files
committed
Planting API
1 parent 5d5a45e commit f673185

5 files changed

Lines changed: 392 additions & 15 deletions

File tree

src/client/java/com/hanprogramer/androids/client/screen/android/SettingsTab.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,7 @@ public SettingsEntry(AndroidProperty property) {
123123
parent.close();
124124
}).build();
125125
} else {
126-
button = ButtonWidget.builder(Text.of("..."), button1 -> {
127-
System.out.println("Clicked");
126+
button = ButtonWidget.builder(Text.of("Unknown"), button1 -> {
128127
}).build();
129128
}
130129
if(property.unused)

src/main/java/com/hanprogramer/androids/entities/android/scripting/SelfBridge.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.hanprogramer.androids.entities.android.scripting;
22

33
import com.hanprogramer.androids.entities.android.AndroidEntity;
4+
import com.hanprogramer.androids.entities.android.util.ContainerExtractor;
5+
import com.hanprogramer.androids.entities.android.util.Seeder;
46
import net.minecraft.block.BlockState;
57
import net.minecraft.block.entity.BlockEntity;
68
import net.minecraft.entity.Entity;
@@ -107,7 +109,7 @@ public void navigateTo(int @Nullable [] pos) throws Exception {
107109
AndroidEntity e = get();
108110
if (e != null) {
109111
if (pos == null) throw new Exception("Can't navigate to 'pos' because pos is null");
110-
e.getNavigation().startMovingAlong(e.getNavigation().findPathTo(pos[0] + 0.5, pos[1], pos[2] + 0.5, 0), (double)0.5F);
112+
e.getNavigation().startMovingAlong(e.getNavigation().findPathTo(pos[0] + 0.5, pos[1], pos[2] + 0.5, 0), (double) 0.5F);
111113
}
112114
}
113115

@@ -172,6 +174,14 @@ public boolean hasItem(String idString) {
172174
return false;
173175
}
174176

177+
@HostAccess.Export
178+
public int getItemFromContainer(int[] targetPos, String idString, int amount) {
179+
var entity = get();
180+
if (entity == null) return 0;
181+
var pos = new BlockPos(targetPos[0], targetPos[1], targetPos[2]);
182+
return ContainerExtractor.takeFromContainerToEntity(entity, entity.getWorld(), pos, idString, amount);
183+
}
184+
175185

176186
/**
177187
* Commands an entity to dump all its inventory items into the container at targetPos.
@@ -303,6 +313,14 @@ public boolean destroyBlock(int[] _targetPos) throws Exception {
303313
} else throw new Exception("not a server world");
304314
}
305315

316+
@HostAccess.Export
317+
public boolean plantSeed(int[] targetPos, String seedId) {
318+
var entity = get();
319+
if(entity == null) return false;
320+
var pos = new BlockPos(targetPos[0], targetPos[1], targetPos[2]);
321+
return Seeder.plantSeedById(entity, entity.getWorld(), pos, seedId);
322+
}
323+
306324
public static boolean isInRange(Entity entity, BlockPos pos, double maxReach) {
307325
if (entity == null || pos == null) return false;
308326
Vec3d eyePos = entity.getEyePos(); // uses eye height for LivingEntity, falls back for others

src/main/java/com/hanprogramer/androids/entities/android/scripting/WorldBridge.java

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,15 @@
22

33
import com.hanprogramer.androids.entities.android.AndroidEntity;
44
import com.hanprogramer.androids.util.BlockQuery;
5+
import net.minecraft.block.Block;
6+
import net.minecraft.block.BlockState;
7+
import net.minecraft.block.Blocks;
58
import net.minecraft.block.CropBlock;
9+
import net.minecraft.registry.Registries;
610
import net.minecraft.server.world.ServerWorld;
11+
import net.minecraft.util.Identifier;
712
import net.minecraft.util.math.BlockPos;
13+
import net.minecraft.world.World;
814
import org.graalvm.polyglot.HostAccess;
915
import org.graalvm.polyglot.Value;
1016
import org.graalvm.polyglot.proxy.ProxyArray;
@@ -31,21 +37,61 @@ public WorldBridge(AndroidEntity e) {
3137
@HostAccess.Export
3238
public Value queryBlocksInAreaById(int[] area, String id) throws Exception {
3339
AndroidEntity e = self.get();
34-
if (e != null) {
35-
if (area.length != 6) throw new Exception("arg1 area is not a BlockArea(array of 6 integers)");
36-
BlockPos pos1 = new BlockPos(area[0], area[1], area[2]);
37-
BlockPos pos2 = new BlockPos(area[3], area[4], area[5]);
38-
39-
var positions = BlockQuery.find((ServerWorld) e.getWorld(), pos1, pos2, id, true);
40-
List<Object> result = new ArrayList<>(positions.size());
41-
for (var p : positions) {
42-
result.add(ProxyArray.fromArray(p.getX(), p.getY(), p.getZ()));
40+
if (e == null) throw new Exception("Can't query blocks: entity null");
41+
if (area == null || area.length != 6) throw new Exception("arg1 area is not a BlockArea(array of 6 integers)");
42+
43+
World world = e.getWorld();
44+
if (!(world instanceof ServerWorld serverWorld)) throw new Exception("Must run on server world");
45+
46+
BlockPos pos1 = new BlockPos(area[0], area[1], area[2]);
47+
BlockPos pos2 = new BlockPos(area[3], area[4], area[5]);
48+
49+
// Normalize input id
50+
String idTrim = (id == null) ? "" : id.trim();
51+
boolean matchAir = idTrim.equalsIgnoreCase("minecraft:air") || idTrim.equalsIgnoreCase("air");
52+
53+
Block wantBlock = null;
54+
if (!matchAir && !idTrim.isEmpty()) {
55+
Identifier parsed = Identifier.tryParse(idTrim);
56+
if (parsed == null) throw new Exception("Invalid id: " + id);
57+
wantBlock = Registries.BLOCK.get(parsed);
58+
}
59+
60+
// Build inclusive extents
61+
int minX = Math.min(pos1.getX(), pos2.getX());
62+
int minY = Math.min(pos1.getY(), pos2.getY());
63+
int minZ = Math.min(pos1.getZ(), pos2.getZ());
64+
int maxX = Math.max(pos1.getX(), pos2.getX());
65+
int maxY = Math.max(pos1.getY(), pos2.getY());
66+
int maxZ = Math.max(pos1.getZ(), pos2.getZ());
67+
68+
List<Object> result = new ArrayList<>();
69+
70+
for (int x = minX; x <= maxX; x++) {
71+
for (int y = minY; y <= maxY; y++) {
72+
for (int z = minZ; z <= maxZ; z++) {
73+
BlockPos p = new BlockPos(x, y, z);
74+
BlockState state = serverWorld.getBlockState(p);
75+
76+
boolean match;
77+
if (matchAir) {
78+
match = state.isAir();
79+
} else if (wantBlock != null) {
80+
match = state.isOf(wantBlock);
81+
} else {
82+
// empty id => match everything; adapt if you want different semantics
83+
match = true;
84+
}
85+
86+
if (match) {
87+
result.add(ProxyArray.fromArray(p.getX(), p.getY(), p.getZ()));
88+
}
89+
}
4390
}
44-
return Value.asValue(ProxyArray.fromList(result));
4591
}
46-
throw new Exception("Can't query blocks");
47-
}
4892

93+
return Value.asValue(ProxyArray.fromList(result));
94+
}
4995
@HostAccess.Export
5096
public boolean blockCropIsMature(int[] pos) throws Exception {
5197
if (pos.length != 3) throw new Exception("arg1 must be a BlockPos (array of 3 integers XYZ)");
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package com.hanprogramer.androids.entities.android.util;
2+
3+
import net.minecraft.block.entity.BlockEntity;
4+
import net.minecraft.entity.Entity;
5+
import net.minecraft.entity.player.PlayerEntity;
6+
import net.minecraft.inventory.Inventory;
7+
import net.minecraft.item.Item;
8+
import net.minecraft.item.ItemStack;
9+
import net.minecraft.item.Items;
10+
import net.minecraft.registry.Registries;
11+
import net.minecraft.server.network.ServerPlayerEntity;
12+
import net.minecraft.util.Identifier;
13+
import net.minecraft.util.math.BlockPos;
14+
import net.minecraft.world.World;
15+
16+
public class ContainerExtractor {
17+
18+
/**
19+
* Try to extract up to `wanted` items of type `itemIdString` from the container at `containerPos`
20+
* and put them into the entity's inventory (fills entity inventory slots when possible).
21+
* <p>
22+
* Returns the number of items actually moved.
23+
* <p>
24+
* Behavior:
25+
* - If itemIdString is null/blank or cannot be resolved to a valid Item, no items are moved.
26+
* - Operates only server-side (returns 0 on client).
27+
* - If the entity does not provide an Inventory interface, returns 0.
28+
*/
29+
public static int takeFromContainerToEntity(Entity entity, World world, BlockPos containerPos, String itemIdString, int wanted) {
30+
if (world == null || world.isClient() || entity == null || containerPos == null) return 0;
31+
if (wanted <= 0) return 0;
32+
33+
// Resolve item id
34+
if (itemIdString == null || itemIdString.isBlank()) return 0;
35+
Identifier id = Identifier.tryParse(itemIdString);
36+
if (id == null) return 0;
37+
Item targetItem = Registries.ITEM.get(id);
38+
if (targetItem == Items.AIR) return 0;
39+
40+
// Ensure container is an Inventory
41+
BlockEntity be = world.getBlockEntity(containerPos);
42+
if (!(be instanceof Inventory container)) return 0;
43+
44+
// Ensure entity has inventory to receive items
45+
if (!(entity instanceof Inventory receiver)) return 0;
46+
47+
int remainingToTake = wanted;
48+
// Iterate container slots, taking from stacks that match the item
49+
for (int i = 0; i < container.size() && remainingToTake > 0; i++) {
50+
ItemStack slotStack = container.getStack(i);
51+
if (slotStack == null || slotStack.isEmpty()) continue;
52+
if (!slotStack.isOf(targetItem)) continue;
53+
54+
// How many we can take from this slot
55+
int takeFromSlot = Math.min(remainingToTake, slotStack.getCount());
56+
57+
// Create a stack to move (respect maxCount of item)
58+
ItemStack moving = slotStack.copy();
59+
moving.setCount(takeFromSlot);
60+
61+
// Try to insert into receiver inventory (merge into existing stacks first, then empty slots)
62+
int stillRemaining = insertStackIntoInventory(receiver, moving);
63+
64+
int actuallyMoved = takeFromSlot - stillRemaining;
65+
if (actuallyMoved > 0) {
66+
// decrement container slot accordingly
67+
ItemStack newSlot = slotStack.copy();
68+
newSlot.decrement(actuallyMoved);
69+
if (newSlot.isEmpty()) container.setStack(i, ItemStack.EMPTY);
70+
else container.setStack(i, newSlot);
71+
72+
remainingToTake -= actuallyMoved;
73+
}
74+
75+
// If receiver couldn't accept any of this slot, try next slot
76+
}
77+
78+
// mark inventories dirty / sync players
79+
container.markDirty();
80+
81+
// If receiver is a custom Inventory object with markDirty, call it (we already modified it via setStack)
82+
try {
83+
receiver.markDirty();
84+
} catch (Throwable ignored) { }
85+
86+
return wanted - remainingToTake;
87+
}
88+
89+
/**
90+
* Insert as much of `toInsert` as possible into `inv`.
91+
* Mutates `toInsert` to reflect remaining items; returns remaining count.
92+
* <p>
93+
* Insertion policy:
94+
* - First merge into existing compatible stacks (same item and ItemStack.areItemsAndComponentsEqual)
95+
* - Then place into empty slots up to item max stack size
96+
*/
97+
private static int insertStackIntoInventory(Inventory inv, ItemStack toInsert) {
98+
if (toInsert == null || toInsert.isEmpty()) return 0;
99+
int remaining = toInsert.getCount();
100+
101+
// Merge into existing stacks
102+
for (int i = 0; i < inv.size() && remaining > 0; i++) {
103+
ItemStack slot = inv.getStack(i);
104+
if (slot == null || slot.isEmpty()) continue;
105+
if (!slot.isOf(toInsert.getItem())) continue;
106+
if (!ItemStack.areItemsAndComponentsEqual(slot, toInsert)) continue;
107+
108+
int maxStack = Math.min(slot.getMaxCount(), toInsert.getMaxCount());
109+
int transferable = Math.min(remaining, maxStack - slot.getCount());
110+
if (transferable <= 0) continue;
111+
112+
ItemStack newSlot = slot.copy();
113+
newSlot.increment(transferable);
114+
inv.setStack(i, newSlot);
115+
remaining -= transferable;
116+
}
117+
118+
// Put into empty slots
119+
for (int i = 0; i < inv.size() && remaining > 0; i++) {
120+
ItemStack slot = inv.getStack(i);
121+
if (slot != null && !slot.isEmpty()) continue;
122+
123+
int putAmount = Math.min(remaining, toInsert.getMaxCount());
124+
ItemStack toPut = toInsert.copy();
125+
toPut.setCount(putAmount);
126+
inv.setStack(i, toPut);
127+
remaining -= putAmount;
128+
}
129+
130+
return remaining;
131+
}
132+
}

0 commit comments

Comments
 (0)