init material grabber

This commit is contained in:
ForestOfLight
2025-04-21 00:44:36 -07:00
Unverified
parent 9a40363c1f
commit 6d7db482c0
6 changed files with 204 additions and 35 deletions
@@ -1,6 +1,9 @@
import { BuilderOptions } from "./BuilderOptions"; import { BuilderOptions } from "./BuilderOptions";
export class Builder { export class Builder {
playerId;
materialInstanceName;
constructor(playerId) { constructor(playerId) {
this.playerId = playerId; this.playerId = playerId;
} }
@@ -5,12 +5,14 @@ import { Structure } from "../Structure/Structure";
import { InstanceOptions } from "./InstanceOptions"; import { InstanceOptions } from "./InstanceOptions";
import { TicksPerSecond } from "@minecraft/server"; import { TicksPerSecond } from "@minecraft/server";
import { InstanceNotPlacedError } from "../Errors/InstanceNotPlacedError"; import { InstanceNotPlacedError } from "../Errors/InstanceNotPlacedError";
import { StructureMaterials } from "../Materials/StructureMaterials";
export class StructureInstance { export class StructureInstance {
options; options;
structure = void 0; structure = void 0;
verifier = void 0; verifier = void 0;
outliner = void 0; outliner = void 0;
materials = void 0;
constructor(instanceName, structureId) { constructor(instanceName, structureId) {
this.structure = new Structure(structureId); this.structure = new Structure(structureId);
@@ -28,6 +30,9 @@ export class StructureInstance {
} }
refreshBox() { refreshBox() {
if (!this.materials)
this.materials = new StructureMaterials(this);
this.materials.refresh();
if (!this.hasLocation()) if (!this.hasLocation())
return; return;
if (!this.outliner) if (!this.outliner)
@@ -134,6 +139,10 @@ export class StructureInstance {
return this.structure.getAllLocations(); return this.structure.getAllLocations();
} }
getActiveMaterials() {
return this.materials;
}
isEnabled() { isEnabled() {
return this.options.isEnabled; return this.options.isEnabled;
} }
@@ -0,0 +1,37 @@
import { MaterialsFormBuilder } from './MaterialsFormBuilder';
import { forceShow } from '../../utils';
import { Builders } from '../Builder/Builders';
export class MaterialsForm {
constructor(player) {
this.player = player;
this.show();
}
show() {
try {
return forceShow(this.player, MaterialsFormBuilder.buildInstanceSelector()).then((response) => {
if (response.canceled)
return;
let selection = response.selection;
const selectedInstanceName = structureCollection.getInstanceNames()[selection];
if (selectedInstanceName) {
this.setActiveInstance(selectedInstanceName);
this.player.sendMessage(`§8Selected instance for material grabber: §2${selectedInstanceName}`);
return;
}
});
} catch (e) {
if (e.message === 'Menu timed out.') {
this.player.sendMessage('§8Menu timed out.');
return;
}
throw e;
}
}
setActiveInstance(instanceName) {
const builder = Builders.get(this.player.id);
builder.materialInstanceName = instanceName;
}
}
@@ -0,0 +1,15 @@
import { MenuFormBuilder } from "../MenuFormBuilder";
export class InstanceFormBuilder {
menuTitle = MenuFormBuilder.menuTitle + ' Material Grabber';
static buildInstanceSelector() {
const allInstanceNameForm = new ActionFormData()
.title(this.menuTitle)
.body('Select an instance:');
structureCollection.getInstanceNames().forEach(instanceName => {
allInstanceNameForm.button(`§2${instanceName}`);
});
return allInstanceNameForm;
}
}
@@ -1,5 +1,5 @@
class MaterialCounter { class StructureMaterials {
instance instance;
materials; materials;
constructor(instance) { constructor(instance) {
@@ -7,6 +7,11 @@ class MaterialCounter {
this.materials = {}; this.materials = {};
} }
refresh() {
this.clear();
this.populateActive();
}
populateAll() { populateAll() {
for (const layer = 0; layer < this.instance.getMaxLayer(); layer++) for (const layer = 0; layer < this.instance.getMaxLayer(); layer++)
this.getLayer(layer) this.getLayer(layer)
@@ -32,47 +37,33 @@ class MaterialCounter {
} }
clear() { clear() {
this.materials = {}; // does this leak memory?? for (const key in this.materials)
delete this.materials[key];
} }
toString() { toString() {
const materials = {};
for (const block of this.instance.getAllBlocks()) {
const itemStack = block?.getItemStack();
const typeId = itemStack?.typeId.replace('minecraft:', '');
if (!typeId) continue;
if (!materials[typeId]) {
materials[typeId] = { count: 0, maxStack: itemStack.maxAmount };
}
materials[typeId].count++;
}
let message = []; let message = [];
for (const blockType in materials) { for (const blockType in this.materials) {
let count = materials[blockType].count; let count = this.materials[blockType].count;
let countStr = ''; let countStr = '';
const maxStack = materials[blockType].maxStack; const stackSize = this.materials[blockType].stackSize;
const fullShulker = 27 * maxStack; const fullShulker = 27 * stackSize;
if (count >= fullShulker) { if (count >= fullShulker)
countStr = `${Math.floor(count / fullShulker)} sb`; countStr = `${Math.floor(count / fullShulker)} sb`;
} if (count > fullShulker)
if (count > fullShulker) {
countStr += ' + '; countStr += ' + ';
}
count %= fullShulker; count %= fullShulker;
if (count >= maxStack) { if (count >= stackSize)
countStr += `${Math.floor(count / maxStack)} stack`; countStr += `${Math.floor(count / stackSize)} stack`;
} if (count > stackSize)
if (count > maxStack) {
countStr += ' + '; countStr += ' + ';
} count %= stackSize;
count %= maxStack; if (count > 0)
if (count > 0) {
countStr += count; countStr += count;
}
message.push(` ${blockType}: ${countStr}`); message.push(` ${blockType}: ${countStr}`);
} }
return message.sort().join('\n'); return message.sort().join('\n');
} }
} }
export { MaterialCounter }; export { StructureMaterials };
@@ -1,5 +1,8 @@
import { BuilderOption } from '../classes/Builder/BuilderOption'; import { BuilderOption } from '../classes/Builder/BuilderOption';
import { world } from '@minecraft/server'; import { EntityComponentTypes, ItemStack, Player, world, system } from '@minecraft/server';
import { MaterialsForm } from '../classes/Materials/MaterialsForm';
import { Builders } from '../classes/Builder/Builders';
import { structureCollection } from '../classes/Structure/StructureCollection';
const builderOption = new BuilderOption({ const builderOption = new BuilderOption({
identifier: 'materialGrabber', identifier: 'materialGrabber',
@@ -8,12 +11,123 @@ const builderOption = new BuilderOption({
howToUse: "Interact with inventories using a paper named 'Material Grabber' to pull structure items from them.", howToUse: "Interact with inventories using a paper named 'Material Grabber' to pull structure items from them.",
}); });
world.beforeEvents.itemUse.subscribe(onItemUse);
world.beforeEvents.playerInteractWithBlock.subscribe(onPlayerInteract); world.beforeEvents.playerInteractWithBlock.subscribe(onPlayerInteract);
world.beforeEvents.playerInteractWithEntity.subscribe(onPlayerInteract); world.beforeEvents.playerInteractWithEntity.subscribe(onPlayerInteract);
function onItemUse(event) {
if (!isActionItem(event.itemStack) || !builderOption.isEnabled(event.source.id))
return;
event.cancel = true;
new MaterialsForm(event.source);
}
function onPlayerInteract(event) { function onPlayerInteract(event) {
// get inventory if (!isActionItem(event.itemStack) || !builderOption.isEnabled(event.player.id))
// analyze active structure items if not analyzed -- there needs to be a way to refresh this that is efficient return;
// find items in inventory that match items in structure const player = event.player;
// transfer to player const target = event.block || event.target;
if (target instanceof Player)
return;
const materials = getActiveMaterials(player);
const targetContainer = target.getComponent(EntityComponentTypes.Inventory)?.container;
const playerContainer = player.getComponent(EntityComponentTypes.Inventory)?.container;
if (!targetContainer || !playerContainer)
return;
event.cancel = true;
system.run(() => {
transferMaterialsToPlayer(targetContainer, playerContainer, materials);
});
}
function isActionItem(itemStack) {
return itemStack?.typeId === 'minecraft:paper' && itemStack?.nameTag === 'Material Grabber';
}
function getActiveMaterials(player) {
const focusedInstanceName = Builders.get(player.id).materialInstanceName;
const structure = structureCollection.get(focusedInstanceName);
if (!structure)
return [];
return structure.getActiveMaterials();
}
function transferMaterialsToPlayer(targetContainer, playerContainer, materials) {
for (let slotIndex = 0; slotIndex < targetContainer.size; slotIndex++) {
const slot = targetContainer.getSlot(slotIndex);
tryTransferToPlayer(slot, playerContainer, materials);
}
}
function tryTransferToPlayer(slot, playerContainer, materials) {
if (slot.hasItem() && materials[slot.typeId]) {
const grabAmount = Math.min(slot.amount, materials[slot.typeId].count);
if (grabAmount > 0)
tryTransferAmountToPlayer(slot, playerContainer, materials, grabAmount);
}
}
function tryTransferAmountToPlayer(slot, playerContainer, materials, grabAmount) {
const itemStack = slot.getItem();
if (canAddItem(playerContainer, itemStack)) {
addItem(playerContainer, itemStack);
materials[slot.typeId].count -= grabAmount;
removeAmount(slot, grabAmount);
}
}
function removeAmount(slot, amount) {
if (amount === slot.amount) {
slot.setItem(void 0);
} else {
slot.amount -= amount;
slot.setItem(slot.getItem());
}
}
function canAddItem(inventory, itemStack) {
if (inventory.emptySlotsCount !== 0) return true;
for (let i = 0; i < inventory.size; i++) {
const slot = inventory.getSlot(i);
if (itemFitsInPartiallyFilledSlot(slot, itemStack)) return true;
}
return false;
}
function itemFitsInPartiallyFilledSlot(slot, itemStack) {
return slot.hasItem() && slot.isStackableWith(itemStack) && slot.amount + itemStack.amount <= slot.maxAmount;
}
function addItem(inventory, itemStack) {
const isItemDeposited = partiallyFilledSlotPass(inventory, itemStack);
if (!isItemDeposited)
emptySlotPass(inventory, itemStack);
}
function partiallyFilledSlotPass(inventory, itemStack) {
for (let slotNum = 0; slotNum < inventory.size; slotNum++) {
const slot = inventory.getSlot(slotNum);
if (isSlotAvailableForStacking(slot, itemStack)) {
const remainderAmount = Math.max(0, (slot.amount + itemStack.amount) - slot.maxAmount);
slot.amount += itemStack.amount - remainderAmount;
if (remainderAmount > 0) {
const remainderStack = new ItemStack(itemStack.typeId, remainderAmount);
addItem(inventory, remainderStack);
}
return true;
}
}
return false;
}
function emptySlotPass(inventory, itemStack) {
for (let slotNum = 0; slotNum < inventory.size; slotNum++) {
const slot = inventory.getSlot(slotNum);
if (!slot.hasItem()) {
slot.setItem(itemStack);
return true;
}
}
return false;
} }