From 16668c67145fef31c614e71581ef7200cf260d9c Mon Sep 17 00:00:00 2001 From: William Mantly Date: Fri, 2 Jun 2023 08:54:08 -0400 Subject: [PATCH] added trade --- nodejs/controller/commands/index.js | 1 + nodejs/controller/commands/trade.js | 412 ++++++++++++++++++++++++++++ nodejs/controller/mc-bot.js | 13 +- nodejs/model/minecraft.js | 69 ++++- 4 files changed, 485 insertions(+), 10 deletions(-) create mode 100644 nodejs/controller/commands/trade.js diff --git a/nodejs/controller/commands/index.js b/nodejs/controller/commands/index.js index 7323c5a..9ef89b4 100644 --- a/nodejs/controller/commands/index.js +++ b/nodejs/controller/commands/index.js @@ -4,4 +4,5 @@ module.exports = { default: require('./default'), fun: require('./fun'), invite: require('./invite'), + trade: require('./trade'), }; diff --git a/nodejs/controller/commands/trade.js b/nodejs/controller/commands/trade.js new file mode 100644 index 0000000..1ae9568 --- /dev/null +++ b/nodejs/controller/commands/trade.js @@ -0,0 +1,412 @@ +'use strict'; + +const {sleep} = require('../../utils'); + + +const botSlots = [0, 1, 2, 3, 9, 10, 11, 12, 18, 19, 20, 21]; +const customerSlots = [5, 6, 7, 8, 14, 15, 16, 17, 23, 24, 25, 26]; + + +function findChestBySign(bot, text){ + let sign = bot.bot.findBlock({ + useExtraInfo: true, + // maxDistance: 1, + matching: (block)=> { + if(block.name.includes('sign') && block.signText.includes(text)){ + return true; + } + } + }); + + return bot.bot.findBlock({ + point: sign.position, + // maxDistance: 1, + useExtraInfo: true, + matching: block => block.name === 'chest' + }); +} + +module.exports = { + '.trade': { + desc: 'Bot will take trade requests', + async function(from){ + /* + todo + + * Do... something if the users chest is full + + */ + + // Make the user has a chest then can add too. + let chestBlock = findChestBySign(this, from); + if(!chestBlock) return this.whisper(from, `You aren't allowed to trade with me...`); + + await this.say('/trade accept'); + let window = await this.once('windowOpen'); + + // If the process is taking to long, just stop + let timeoutCheck = setTimeout(()=>{ + this.bot.closeWindow('window'); + this.bot.removeAllListeners('windowOpen'); + this.whisper(from, `I have things to do, I cant wait on you all day!`) + }, 30000); + + // Check to see if the remote user has agreed to the trade. + let confirmationCheck = setInterval(async ()=>{ + if(window.containerItems().filter(item => item?.slot == 53)[0].name == 'lime_dye'){ + this.bot.moveSlotItem(37, 37); + } + }, 500); + + // Clean up when the trade is done + await this.once('windowClose'); + clearInterval(confirmationCheck); + + // If the trade took so long it timed out, just kill the whole thing. + if(timeoutCheck._destroyed) return; + clearTimeout(timeoutCheck) + + // Give MC a moment + await sleep(1000); + + let isPutAway = await this.putInChest(chestBlock) + await this.whisper(from, `I put ${isPutAway ? 'all' : 'some'} items in your chest.`); + } + } +} + + + + + + + + + + + + + + + + + + + +/* + + +const { countCustomerPayment } = require('../parts/countCustomerPayment.js'); +const { getCustomerItems } = require('../parts/getCustomerItems.js'); +const { findContainerItem } = require('../parts/findContainerItem.js'); +const { minecraftBot } = require('../../../modules/minecraftBot.js'); +const mcData = require('minecraft-data')(minecraftBot.version); + +const { once } = require('../../../functions/once.js'); +const { timeOut } = require('../../../functions/timeOut.js'); +const { toMinecraftChat } = require('../../../functions/toMinecraftChat.js'); + +const { findOrCreateBankItemWithRelation } = require('../../bank/parts/findOrCreateBankItemWithRelations.js'); +const { toDiscordChat } = require('../../../functions/toDiscordChat.js'); + + + +let checkCustomerFirstConfirmInterval; +let checkCustomerSecondConfirmInterval; +let putItemInterval; +let firstTimeout; +let secondTimeout; + +let windowOpened; +let timedOut; +let isFinished; + +const firstTimeoutDelay = 30000; +const secondTimeoutDelay = 20000; + +function CJTradeRequest(targetPlayer, itemToGive, itemToGiveAmount, itemToReceive, itemToReceiveAmount) { + windowOpened = false; + isFinished = false; + timedOut = false; + + // Item that is not the currency. + let fullItemName = itemToGive.nameWithTradeName ?? itemToReceive.nameWithTradeName; + let itemAmount = itemToGive.nameWithTradeName ? itemToGiveAmount : itemToReceiveAmount; + + let tradePromise = new Promise((resolve, reject) => { + console.log("trade request started"); + minecraftBot.chat(`/trade ${targetPlayer}`); + minecraftBot.once('windowOpen', async (window) => { + windowOpened = true; + if (timedOut == true) { + return; + } + console.log("trade window opened"); + + // List of items in inventory + let windowInventoryItems = window.items(); + + function windowInventoryItemsCount() { + let inventoryItemCount = 0; + + for (let item of windowInventoryItems) { + if (item.name == itemToGive.item_name) { + inventoryItemCount += parseInt(item.count); + } + } + return inventoryItemCount; + } + console.log("inventory item count: " + windowInventoryItemsCount()); + + // If itemToGive is diamond round down to whole number and pay difference in diamondBank + let roundingDifference = 0; + if (itemToGive.item_name == "diamond" && itemToGive.isToken == false) { + let itemToGiveAmountUnrounded = itemToGiveAmount; + itemToGiveAmount = Math.floor(itemToGiveAmount); + roundingDifference = itemToGiveAmountUnrounded - itemToGiveAmount; + } + + let takeItemDelay = 50; + let i = 0; + let currentSlotAmount = 0; + let movedAmount = 0; + + let itemToMove; + let amountLeft; + + let putItemInTrade = async () => { + itemToMove = await findContainerItem(window.items(), itemToGive, itemToGive.itemEnchants); + // amountLeft is the amount of items left to move + amountLeft = itemToGiveAmount - movedAmount; + + if (itemToMove !== null) { + // Move entire slot if is less than whats left + if (amountLeft >= itemToMove.count) { + amountToMove = itemToMove.count + console.log("amount to move is all items in inv slot, amount: ", amountToMove); + } + + // Move only specific amount of slot if slot itemToMove count is more than whats left + else if (amountLeft < itemToMove.count) { + amountToMove = amountLeft; + console.log("amount to move is less than in the slot, amount: ", amountToMove); + } + + // Move stack size if amountToMove is bigger than a stack + if (currentSlotAmount + amountToMove > itemToGive.stackSize) { + amountToMove = itemToGive.stackSize - currentSlotAmount; + console.log("total amount of slot will be more than stack size, updated amount: ", amountToMove); + } + } + + if (itemToGiveAmount > movedAmount) { + itemSlot = itemToMove.slot; + let options = { + itemType: itemToGive.mcId, + destStart: botSlots[i], + // destEnd is until (excluding itself) + destEnd: botSlots[i] + 1, + sourceStart: itemSlot, + sourceEnd: itemSlot + 1, + count: amountToMove, + } + currentSlotAmount += amountToMove; + minecraftBot.transfer(options) + movedAmount = parseInt(movedAmount + amountToMove); + console.log(" amountToMove / movedAmount / total needed amount", amountToMove, movedAmount, amount); + console.log("current slot amount / stack size", currentSlotAmount, itemToGive.stackSize); + if (currentSlotAmount >= itemToGive.stackSize) { + console.log("1 slot up!"); + i++; + currentSlotAmount = 0; + } + } + if (itemToGiveAmount == movedAmount) { + clearInterval(putItemInterval); + //do first confirm + setTimeout(() => { + minecraftBot.moveSlotItem(37, 37); + }, 70); + } + } + // Start putting items in trade window. + console.log("give token: ", itemToGive.isToken) + if (itemToGive.isToken == false) { + putItemInterval = setInterval(putItemInTrade, takeItemDelay); + } + // If token transaction do not put item in trade window and press first confirm. + else if (itemToGive.isToken == true) { + setTimeout(() => { + minecraftBot.moveSlotItem(37, 37); + }, 70); + } + + let missingDiamonds = null; + let missingDiamondsRounded = null; + let windowItems; + let paymentCount; + let bankItem; + + let validateTrade = async () => { + windowItems = window.containerItems(); + paymentCount = await countCustomerPayment(windowItems, itemToReceive, itemToReceive.itemEnchants); + + // Check if payment amount is less than required amount + if (paymentCount < itemToReceiveAmount) { + if (itemToReceive.item_name == "diamond") { + missingDiamonds = itemToReceiveAmount - paymentCount; + missingDiamondsRounded = Math.round(missingDiamonds * 100) / 100; + + // Check if it has the missing diamonds in bank + bankItem = await findOrCreateBankItemWithRelation(targetPlayer) + if (bankItem.balance < missingDiamonds) { + toMinecraftChat(targetPlayer, "You did not pay enough, and not enough in bank!"); + minecraftBot.closeWindow(window); + return reject("Not enough paid and not enough in bank."); + } + } + else { + toMinecraftChat(targetPlayer, "You did not pay enough of the item!"); + minecraftBot.closeWindow(window); + return reject("not enough paid"); + } + } + // Check for illegal items + for (let windowItem of windowItems) { + // Item is named with anvil and bigger stack size than 1 + let itemNickJson = windowItem?.nbt?.value?.display?.value?.Name?.value + let itemNick = itemNickJson ? JSON.parse(itemNickJson).text : null; + if (itemNick) { + let itemStackSize = mcData.itemsByName[windowItem.name].stackSize; + if (itemStackSize > 1) { + toMinecraftChat(targetPlayer, "Only items with stack size of 1 may be named!"); + minecraftBot.closeWindow(window); + return reject("named item"); + } + } + + // Item is is a map + if (windowItem.name == "filled_map") { + toMinecraftChat(targetPlayer, "You put an illegal piece of junk in trade window."); + minecraftBot.closeWindow(window); + return reject(`Illegal item "filled_map" in trade window.`); + } + } + } + + let customerConfirmItem; + let checkCustomerFirstConfirm = async () => { + customerConfirmItem = window.containerItems().filter(item => item?.slot == 53); + if (window.findContainerItem("magenta_dye") !== null && customerConfirmItem[0]?.name == "magenta_dye") { + clearInterval(checkCustomerFirstConfirmInterval); + await validateTrade(); + checkCustomerSecondConfirmInterval = setInterval(checkCustomerSecondConfirm, secondConfirmIntervalDelay); + } + } + + let checkCustomerSecondConfirm = async () => { + console.log("checkCustomerSecondConfirm"); + // Check if item in slot 54 is lime dye + customerConfirmItem = window.containerItems().filter(item => item?.slot == 53); + if (window.findContainerItem("lime_dye") != null && customerConfirmItem[0]?.name == "lime_dye") { + console.log("customer did second confirm"); + clearInterval(checkCustomerSecondConfirmInterval); + await validateTrade(); + bankItem = await findOrCreateBankItemWithRelation(targetPlayer) + // Buy: + if (itemToReceive.item_name == "diamond") { + if (bankItem.balance >= missingDiamonds && missingDiamonds != null) { + bankItem.balance -= missingDiamonds; + bankItem.save(); + toMinecraftChat(targetPlayer, `You paid ${missingDiamondsRounded} diamonds from your bulba bank account!`); + toDiscordChat(`Paid ${missingDiamondsRounded} diamond token and ${itemToReceiveAmount - missingDiamonds} diamond.`); + } + // Add overpaid diamonds to bank + else if (paymentCount > itemToReceiveAmount) { + let overpaidDiamonds = paymentCount - itemToReceiveAmount; + let overpaidDiamondsRounded = Math.round(overpaidDiamonds * 100) / 100; + + bankItem.balance += overpaidDiamonds; + bankItem.save(); + toMinecraftChat(targetPlayer, `You overpaid ${overpaidDiamondsRounded} diamond. It has been added to your bulba bank account!`); + toDiscordChat(`Overpaid ${overpaidDiamondsRounded} diamond.`); + } + } + // Sell: Give all diamonds in tokens to customer + else if (itemToGive.item_name == "diamond" && itemToGive.isToken == true) { + console.log("Gave all currency/item in tokens to customer."); + bankItem.balance += itemToGiveAmount; + bankItem.save(); + } + // Sell: Give rounding difference to customer + else if (itemToGive.item_name == "diamond" && roundingDifference > 0) { + console.log("Gave rounding difference to customer."); + bankItem.balance += roundingDifference; + bankItem.save(); + } + console.log("payment: ", paymentCount); + let customerItems = getCustomerItems(windowItems); + minecraftBot.moveSlotItem(37, 37); + once('windowClose').then(() => { + return resolve(customerItems); + }); + } + }; + + // Check for secondConfirm every 0.05 seconds until trade closes + let secondConfirmIntervalDelay = 50; + checkCustomerFirstConfirmInterval = setInterval(checkCustomerFirstConfirm, secondConfirmIntervalDelay); + + // Delay because when trade succesfull it closes too + await once('windowClose'); + await timeOut(1000); + return reject("Trade window closed."); + }); + + // 30 seconds timeout add 20 seconds if in trade window + firstTimeout = setTimeout(() => { + console.log("timeout!!"); + if (isFinished == true) { + return; + } + else if (windowOpened == false) { + toMinecraftChat(targetPlayer, `Trade request timed out. If you only want to check the price please use "price ${fullItemName} ${itemAmount}" instead.`); + minecraftBot.removeAllListeners('windowOpen'); + timedOut = true; + return reject("timed out"); + } + else { + //if in trade window + secondTimeout = setTimeout(() => { + console.log("second timeout!!"); + if (windowOpened == true && timedOut == false) { + toMinecraftChat(targetPlayer, "Trade request timed out."); + minecraftBot.removeAllListeners('windowOpen'); + return reject("timed out in trade window"); + } + }, secondTimeoutDelay); + } + }, firstTimeoutDelay); + + }); + tradePromise + .then(() => { + clearInterval(checkCustomerFirstConfirmInterval); + clearInterval(checkCustomerSecondConfirmInterval); + clearInterval(putItemInterval); + clearTimeout(firstTimeout); + clearTimeout(secondTimeout); + isFinished = true; + }) + .catch((err) => { + clearInterval(checkCustomerFirstConfirmInterval); + clearInterval(checkCustomerSecondConfirmInterval); + clearInterval(putItemInterval); + clearTimeout(firstTimeout); + clearTimeout(secondTimeout); + isFinished = true; + }); + + return tradePromise; +} +module.exports = { CJTradeRequest };*/ \ No newline at end of file diff --git a/nodejs/controller/mc-bot.js b/nodejs/controller/mc-bot.js index c3d5324..a47f57b 100644 --- a/nodejs/controller/mc-bot.js +++ b/nodejs/controller/mc-bot.js @@ -14,19 +14,19 @@ for(let name in conf.mc.bots){ let bot = new CJbot({name, host: conf.mc.host, ...conf.mc.bots[name]}); CJbot.bots[name] = bot; - for(let command of conf.mc.bots[name].commands || ['default']){ - for(let [name, toAdd] of Object.entries(commands[command])){ bot.addCommand(name, toAdd) } } - + bot.on('onReady', async function(argument) { + await sleep(1000); + bot.bot.setControlState('jump', true); + setTimeout(()=> bot.bot.setControlState('jump', false), 5000) + }) } - - (async ()=>{ try{ for(let name in CJbot.bots){ @@ -37,11 +37,8 @@ try{ await sleep(30000); } - } - }catch(e){ - console.log('!!!!!!!! error:', e) }})() diff --git a/nodejs/model/minecraft.js b/nodejs/model/minecraft.js index 1b0d42e..76f11de 100644 --- a/nodejs/model/minecraft.js +++ b/nodejs/model/minecraft.js @@ -1,7 +1,8 @@ 'use strict'; -const {sleep} = require('../utils'); const mineflayer = require('mineflayer'); +const Vec3 = require('vec3'); +const {sleep} = require('../utils'); class CJbot{ @@ -111,7 +112,7 @@ class CJbot{ // Call the internal listeners when the bot is ready for(let callback of this.listeners.onReady || []){ console.log('calling listener', callback) - await callback(this) + await callback.call(this) } this.isReady = true; @@ -222,6 +223,14 @@ class CJbot{ this.__doCommand(message.toString().split(' ')[0], '.invite'); } + if(message.toString().includes(' wants to trade with you!')){ + // teleport invite + + console.log('found Trade', message.toString().split(' ')[0]) + this.__doCommand(message.toString().split(' ')[0], '.trade'); + } + + if(message.toString().includes('You are combat tagged by')){ try{ this.combatTag = true; @@ -316,6 +325,62 @@ class CJbot{ return this.bot.players; } + + __blockOrVec(thing){ + if(thing instanceof Vec3.Vec3) return this.bot.blockAt(thing); + if(thing.constructor && thing.constructor.name === 'Block') return thing; + + throw new Error('Not supported block identifier'); + } + + + async __nextContainerSlot(window, item) { + let firstEmptySlot = false; + + await window.containerItems(); + + for(let slot in window.slots.slice(0, window.inventoryStart)){ + if(window.slots[slot] === null ){ + if(!Number.isInteger(firstEmptySlot)) firstEmptySlot = Number(slot); + continue; + } + if(item.type === window.slots[slot].type && window.slots[slot].count < window.slots[slot].stackSize){ + return slot; + } + } + + return firstEmptySlot; + } + + async putInChest(block, blockName, amount) { + block = this.__blockOrVec(block); + + this.bot.openContainer(block); + let window = await this.once('windowOpen'); + + for(let item of window.slots.slice(window.inventoryStart).filter(function(item){ + if(!item) return false; + if(blockName && blockName !== item.name) return false; + return true; + })){ + let currentSlot = Number(item.slot); + if(!window.slots[currentSlot]) continue; + + if(amount && !amount--) return; + let chestSlot = await this.__nextContainerSlot(window, item); + console.log('next slot', chestSlot); + await this.bot.moveSlotItem(currentSlot, chestSlot) + + let res = await this.putInChest(...arguments); + if(res === false) return amount ? amount : false; + } + + await this.bot.closeWindow(window); + + return amount ? amount : true; + } + + } module.exports = {CJbot};