|
| 1 | +// Script example for ScriptAPI |
| 2 | +// Author: Nperma <https://github.com/nperma> |
| 3 | +// Project: https://github.com/JaylyDev/ScriptAPI |
| 4 | + |
| 5 | +import { world, system, World, Player, ItemStack } from '@minecraft/server'; |
| 6 | +import { ActionFormData, ModalFormData, MessageFormData, FormCancelationReason } from '@minecraft/server-ui'; |
| 7 | + |
| 8 | +const { setDynamicProperty: SDP, getDynamicProperty: GDP, getDynamicPropertyIds: IDS } = World.prototype, |
| 9 | + scb = world.scoreboard; |
| 10 | + |
| 11 | +/** @param {Player} player @param {ActionFormData} form */ |
| 12 | +async function FORCE_OPEN(player, form) { |
| 13 | + while (true) { |
| 14 | + let v = await form.show(player); |
| 15 | + if (!v || v.cancelationReason !== FormCancelationReason.UserBusy) return v; |
| 16 | + } |
| 17 | +} |
| 18 | +/** @param {Player} player @returns {number} */ |
| 19 | +function gM(player) { |
| 20 | + const ob = scb.getObjective('money') ? scb.getObjective('money') : scb.addObjective('money'); |
| 21 | + return ob.getScore(player) || 0; |
| 22 | +} |
| 23 | + |
| 24 | +/** @param {Player} player @param {number} amount */ |
| 25 | +function aM(player, amount) { |
| 26 | + system.run(() => { |
| 27 | + const ob = scb.getObjective('money') ? scb.getObjective('money') : scb.addObjective('money'); |
| 28 | + ob.setScore(player, gM(player) + amount); |
| 29 | + }); |
| 30 | +} |
| 31 | + |
| 32 | +export class ShopUI { |
| 33 | + SHOP; |
| 34 | + SHOP_TITLE; |
| 35 | + SENDERS; |
| 36 | + STRUCTURE; |
| 37 | + |
| 38 | + /** @param {string} title - shop title @param {Array} structure - the shop structure @param {Player | Player[]} ShowFormTo - player's will show this shop form */ |
| 39 | + constructor(title = 'Shop - UI', structure = [], ShowFormTo = undefined) { |
| 40 | + if (!Array.isArray(structure)) throw new TypeError('Structure must be an array!'); |
| 41 | + this.SENDERS = ShowFormTo; |
| 42 | + this.STRUCTURE = structure; |
| 43 | + this.SHOP_TITLE = title; |
| 44 | + this.SHOP = new ActionFormData().title(title.toString()); |
| 45 | + this.#defined(); |
| 46 | + } |
| 47 | + |
| 48 | + #defined() { |
| 49 | + if (!this.SENDERS) return; |
| 50 | + |
| 51 | + if (this.SENDERS instanceof Player) { |
| 52 | + this.#showCategoryForm(this.SENDERS, this.STRUCTURE, this.SHOP_TITLE); |
| 53 | + } else if (Array.isArray(this.SENDERS)) { |
| 54 | + for (const sender of this.SENDERS) this.#showCategoryForm(sender, this.STRUCTURE, this.SHOP_TITLE); |
| 55 | + } |
| 56 | + } |
| 57 | + |
| 58 | + /** |
| 59 | + * @param {Player} player |
| 60 | + * @param {any[]} categories |
| 61 | + * @param {string | import("@minecraft/server").RawMessage} path |
| 62 | + */ |
| 63 | + async #showCategoryForm(player, categories, path) { |
| 64 | + const FORM = new ActionFormData().title(path); |
| 65 | + for (const [CATEGORY_NAME, _, CATEGORY_TEXTURE = ''] of categories) FORM.button(CATEGORY_NAME, CATEGORY_TEXTURE); |
| 66 | + |
| 67 | + const FORM_SELECTION = await FORCE_OPEN(player, FORM); |
| 68 | + if (FORM_SELECTION.canceled) return; |
| 69 | + |
| 70 | + const selected = categories[FORM_SELECTION.selection]; |
| 71 | + if (!Array.isArray(selected)) return; |
| 72 | + |
| 73 | + const [CATEGORY_NAME, ITEMS] = selected; |
| 74 | + const PATH = `${path}::${CATEGORY_NAME}`.replace(' ', '_'); |
| 75 | + |
| 76 | + if (Array.isArray(ITEMS[0])) this.#showCategoryForm(player, ITEMS, PATH); |
| 77 | + else this.#showItemForm(player, ITEMS, PATH, CATEGORY_NAME); |
| 78 | + } |
| 79 | + |
| 80 | + /** |
| 81 | + * @param {Player} player |
| 82 | + * @param {any} items |
| 83 | + * @param {string} categoryPath |
| 84 | + * @param {string | import("@minecraft/server").RawMessage} categoryName |
| 85 | + */ |
| 86 | + async #showItemForm(player, items, categoryPath, categoryName) { |
| 87 | + player.sendMessage(JSON.stringify({ ...arguments }, null, 4)); |
| 88 | + const item_data = items; |
| 89 | + if (typeof item_data === 'object' && !Array.isArray(item_data)) { |
| 90 | + const STOCKED_ID = `${categoryPath}::${categoryName}`; |
| 91 | + if (!IDS.call(world).find((/** @type {string} */ id) => id === STOCKED_ID)) SDP.call(world, STOCKED_ID, item_data?.stock || 64); |
| 92 | + |
| 93 | + const currentStock = GDP.call(world, STOCKED_ID); |
| 94 | + if (currentStock <= 0) return new MessageFormData().title(categoryName).body(`§cStock is empty, please wait for restock.`).button2('Close').show(player); |
| 95 | + new ModalFormData() |
| 96 | + .title(categoryName) |
| 97 | + .slider(`§7${categoryPath.replace(/::/g, '/')}\n§e» Money: §a$${gM(player)}\n§e» Item ID: ${item_data?.item}\n§e» Price per item: §2$${item_data?.price}\n§e» §7Stock: (${currentStock})`, 1, Math.min(item_data?.max, currentStock) || 1, 1) |
| 98 | + .show(player) |
| 99 | + .then((p) => { |
| 100 | + if (p.canceled) return; |
| 101 | + |
| 102 | + /** |
| 103 | + * @type {number} |
| 104 | + */ |
| 105 | + // @ts-ignore |
| 106 | + const amount = p.formValues[0]; |
| 107 | + const totalCost = amount * item_data?.price; |
| 108 | + |
| 109 | + if (gM(player) < totalCost) |
| 110 | + return new MessageFormData() |
| 111 | + .title(categoryName) |
| 112 | + .body(`§cYou don't have enough money!\n§eYou need §a$${totalCost - gM(player)}`) |
| 113 | + .button2('Close') |
| 114 | + .show(player); |
| 115 | + |
| 116 | + aM(player, -totalCost); |
| 117 | + SDP.call(world, STOCKED_ID, currentStock - amount); |
| 118 | + player.getComponent('inventory').container.addItem(new ItemStack(item_data?.item, amount)); |
| 119 | + player.sendMessage(`§aSuccessfully bought ${amount}x ${item_data?.item}`); |
| 120 | + }); |
| 121 | + } |
| 122 | + } |
| 123 | +} |
0 commit comments