So i have a bot with one single command “/fight” and it take in another user as it argument. If you run that command it will prompt the user in that command I will call it “player 2” or “p2” for short to press a button attach to the reply the bot send to accept to fight player 1 or p1 (The person who call the command). I need p2 to press the button cus I need that player interaction to be able to send 2 hidden message. So I do all that in this code:
const p1Interaction = interaction // getting p1 interaction
var p2User = options.getUser("player")
var p2Interaction
const button = new MessageActionRow().addComponents(
new MessageButton()
.setCustomId("fight_accept")
.setStyle("SUCCESS")
.setLabel("Accept")
)
const Player2Filter = (i) => { return i.user.id === p2User.id }
// send the broad message with the accept button
const broadMessage = await interaction.reply({
content: `<@${p2User.id}> Please press accept to start the fight`,
components: [button],
fetchReply: true
})
// getting the button press and turn that interaction into p2 interaction
await broadMessage.awaitMessageComponent({ filter: Player2Filter, componentType: "BUTTON", max: 1, time: 60000, errors: ['time'], })
.then(async i => {
p2Interaction = i
})
.catch(collected => {
console.log("Error")
})
So after getting p1Interaction and p2Interaction, the bot send 2 hidden message to both of them and this message is call the control panel for them to input stuff to use the bot. And it look like this:
const p1ControlPanelMessage = await p1Interaction.followUp({
embeds: [controlPanelEmbed],
ephemeral: true,
components: [controlPanelActionRow1, controlPanelActionRow2]
})
const p2ControlPanelMessage = await p2Interaction.reply({
embeds: [controlPanelEmbed
.setColor("RED")],
ephemeral: true,
components: [controlPanelActionRow1, controlPanelActionRow2],
fetchReply: true
})
The controlPanelActionRow1
,controlPanelActionRow2
and controlPanelEmbed
are define earlier and removed for abbreviation sake. So after that it define some more variable to be use for handling the game. Then came a while loop, this while is the main game loop and to handle most of the game stuff. So at the start of this while loop it check who turn it is and set some variables accordingly so here the code for that:
while (broadManager.scale < 5 && broadManager.scale > -5) {
if (broadManager.isP1Turn) {
controlPanelMessage = p1ControlPanelMessage
currentPlayer = player1
currentUsername = p1Interaction.user.username
currentRow = 1
currentPlayer.boneToken += 100
} else {
controlPanelMessage = p2ControlPanelMessage
currentPlayer = player2
currentUsername = p2Interaction.user.username
currentRow = 0
}
BroadManager and Player are just object to help me handle the game and it just this.
// blank() is a function that I use to construct blank spaces on the broad
class BroadManager {
constructor () {
this.isP1Turn = true
this.broad = [
[blank(), blank(), blank(), blank()],
[blank(), blank(), blank(), blank()],
]
this.scale = 0
}
}
//Player
class Player {
constructor (userID = 0, deck = []) {
this.userID = userID
this.deck = deck
this.boneToken = 0
this.sacMade = 0
this.misplay = 0
this.hand = []
for (let i = 0; i < 4; i++) {
const c = choices(deck)[0]
this.hand.push(c)
this.deck.splice(deck.indexOf(c), 1)
}
}
}
After that it take button press from the corresponding player control panel like this.
await controlPanelMessage.awaitMessageComponent({ filter, componentType: "BUTTON", max: 1, time: 3600000, errors: ['time'], })
.then(async i => {
int = i // setting this to a variable so I can use it later
state = i.customId
})
.catch(collected => {
console.log("Error")
})
So then it go into an if tree (ik it bad but I only have like 4 commands) I will only focus on what branch of that if tree broken rn so let see. It use to placing card down onto the broad. So first it scan the player hand and remove all the duplicate card then add all of those card on an actionRow as button:
// make an action row to attach to message
playCardActionRow = new MessageActionRow()
// cycling through the player hand and remove all duplicate card
var handNoDup = []
currentPlayer.hand.forEach((card) => {
var item = [card.name, card.portrait]
var flag = false
handNoDup.forEach((i) => {
if (i.join("") == item.join("")) {
flag = true
}
})
if (!flag) {
handNoDup.push(item)
}
})
// then add them to the action row as button for the user to press
handNoDup.forEach((card) => {
playCardActionRow.addComponents(
new MessageButton()
.setStyle("SECONDARY")
.setCustomId(`fight_play_select_${card[0].toLowerCase()}`)
.setLabel(`Play ${card[0]}`)
.setEmoji(card[1])
)
})
// adding a cancel button
playCardActionRow.addComponents(
new MessageButton()
.setStyle("DANGER")
.setCustomId("fight_play_cancel")
.setLabel("Cancel")
)
// sending the message to get user input on what card should be place
const cardSelectionMessage = await int.reply({
content: "Which card do you want to play",
components: [playCardActionRow],
fetchReply: true
})
then ofc it will listen for button press using this:
var cardToPlace
var temp
var flag = false
// actually getting the player input and turn it into a cardName
await cardSelectionMessage.awaitMessageComponent({ filter, componentType: "BUTTON", max: 1, time: 60000, errors: ['time'], })
.then(async i => {
temp = i
if (!(i.customId === "fight_play_cancel")) {
cardToPlace = i.customId.substr(18)
} else {
flag = true
}
})
.catch(collected => {
console.log("Error")
})
the flag use for canceling the interaction using a continue statement to get out of the if statement and go back to the start of the while loop. Because in my game there a cost to summoning card and those are blood and bone so there some check for that here for the blood cost:
if (getCardByName(cardToPlace).bloodCost > 0) {
console.log("doing blood cost")
sacCardActionRow = new MessageActionRow()
//there code here for adding 4 option with value from "0" to "3"
//remove to shorten this code block
//ofc sending a message to get user input
const sacSelectionMessage = await temp.update({
content:
`This card need ${getCardByName(cardToPlace).bloodCost} sacrifice.n` +
`Please select ${getCardByName(cardToPlace).bloodCost} column to sacrifice creature from`,
ephemeral: true,
components: [sacCardActionRow],
fetchReply: true
})
//getting the input
await sacSelectionMessage.awaitMessageComponent({ filter, componentType: "SELECT_MENU", max: 2, time: 60000, errors: ['time'], })
.then(async i => {
temp = i
if (i.customId === "fight_play_select_column_sac") {
i.values.forEach((t) => {
sacCol.push(parseInt(t))
})
}
})
.catch(collected => {
console.log(collected)
})
//make sure user can't sacrifice blank space and break out of to the while loop
//if that the case
let flag = true
sacCol.forEach((col) => {
if (broadManager.broad[currentRow][col].id == blankId) {
flag = false
}
})
if (!flag) {
temp.update({
content:
"You can't sacrifice a blank space. Please press the play card button again from the Control Paneln" +
"You can now dismiss this message",
components: []
})
continue
}
and here for bone cost which is much simpler:
if (getCardByName(cardToPlace).boneCost > 0) {
if (currentPlayer.boneToken >= getCardByName(cardToPlace).boneCost) {
useBone = true
} else {
await temp.update({
content:
"You don't have enough bone token to play this card. Please press the play card button again from the Control Paneln" +
"You can now dismiss this message",
components: []
})
continue
}
}
useBone
and sacCol
are just a bool and a list so that I can use them later. Then it let the player choose a column to play the card onto. So here taht code
colSelectionActionRow = new MessageActionRow().addComponents(
//adding 4 option with value from "0" to "3"
//removed to make the code block shorter
)
console.log("message")
// sending the column selection message
const colSelectionMessage = await temp.update({
content: "Which column do you want to play it in",
components: [colSelectionActionRow],
fetchReply: true
})
// taking user input and turn it into useable shit
var placeCol
await colSelectionMessage.awaitMessageComponent({ filter, componentType: "SELECT_MENU", max: 1, time: 60000, errors: ['time'], })
.then(async i => {
temp = i
if (i.customId === "fight_play_select_column") {
placeCol = parseInt(i.values)
}
})
.catch(collected => {
console.log(collected)
})
Then it do some final calculation and place the card down.
if (broadManager.broad[currentRow][placeCol].id != blankId && !sacCol.includes(placeCol)) {
temp.update({
content:
"You can't play that card here, there another card in it place. Please press the play card button again from the Control Paneln" +
"You can now dismiss this message",
components: []
})
continue
}
//killing all sacrifice card
sacCol.forEach((col) => {
broadManager.killCard(col, currentRow, currentPlayer)
//increasing the sacMade stat
currentPlayer.sacMade++
currentPlayer.boneToken++
})
if (useBone) {
currentPlayer.boneToken -= getCardByName(cardToPlace).boneCost
}
//placing the card down
broadManager.placeCard(placeCol, currentRow, cardToPlace)
//send an message to tell the player it done
temp.update({
content:
"The card has been place you can now dismiss all messages that have been send to you during this placement process. But not the control panel message of course." +
"You can now also dismiss this message. The broad also get updated check it out!",
components: []
})
So that all of my code now let get to the problem. So after placing down 2-3 card the bot starting to slow down and after the 4/5th card it just crash saying this in the log DiscordAPIError: Unknown interaction
on line 537
which is this line:
const colSelectionMessage = await temp.update({
content: "Which column do you want to play it in",
components: [colSelectionActionRow],
fetchReply: true
})
Saying that temp is an unknown interaction. idk what wrong, it can this for 4 times in a row then it just crash on the 5th time. No time isn’t an issue cus i try running this using a different button that just a blank button and pressing it for hour and the bot still work just fine. Here all of my code in this github repo. Just create bot application and use this code, if you want to reproduce the bug and get some more info.
If you need more info just dm me (khanhFG#3753) or comment, I will try my best to reply asap. Sorry for some broken English if there any in the question.