2619 lines
107 KiB
JavaScript
2619 lines
107 KiB
JavaScript
const { Telegraf, Scenes, session, Markup, Stage, Composer } = require('telegraf');
|
||
const schedule = require('node-schedule');
|
||
const path = require('path');
|
||
const fs = require('fs');
|
||
const { faker, fakerRU } = require('@faker-js/faker');
|
||
// Подключаем необходимые библиотеки
|
||
const { Op } = require('sequelize');
|
||
const sequelize = require('./db'); // Подключение базы данных
|
||
// Подключаем обработчики
|
||
const utils = require('./utils');
|
||
const handlers = require('./handlers');
|
||
const {
|
||
phones,
|
||
expToUp,
|
||
UserModel,
|
||
CharacterModel,
|
||
InventoryModel,
|
||
ItemsModel,
|
||
Enemy,
|
||
Location,
|
||
Battle,
|
||
StolenCardsModel,
|
||
WorldModel,
|
||
PropertyModel,
|
||
DailyModel,
|
||
logs
|
||
} = global.config
|
||
const rpg = new Composer();
|
||
|
||
rpg.use(async (ctx, next) => {
|
||
if (ctx.update.edited_channel_post) return console.log(`[RPG] Channel post updated`);
|
||
let id = ctx.from.id
|
||
global.messagesCounter.inc({ type: ctx.updateType });
|
||
let username = ctx.from.username;
|
||
if (username == null) username = ctx.from.id;
|
||
const currentTime = utils.getCurrentTime();
|
||
|
||
switch (ctx.updateType) {
|
||
case 'message':
|
||
console.log(`${currentTime}: [RPG] ${username}: ${ctx.update.message.text}`);
|
||
break;
|
||
|
||
case 'callback_query':
|
||
console.log(`${currentTime}: [RPG] ${username}: ${ctx.update.callback_query.data}`);
|
||
break;
|
||
|
||
case 'edited_message':
|
||
console.log(`${currentTime}: [RPG] ${username} edited: ${ctx.update.edited_message.text}`);
|
||
break;
|
||
|
||
default:
|
||
console.log(`${currentTime}: [RPG] Unknown update type:`, ctx.update);
|
||
}
|
||
|
||
await next(); // Передаем управление следующему middleware
|
||
});
|
||
|
||
const votesFile = path.join(__dirname, '/json/votes.json');
|
||
|
||
// Функция для загрузки данных из JSON
|
||
function loadVotes() {
|
||
if (!fs.existsSync(votesFile)) return {};
|
||
return JSON.parse(fs.readFileSync(votesFile, 'utf8'));
|
||
}
|
||
|
||
// Функция для сохранения данных в JSON
|
||
function saveVotes(votes) {
|
||
fs.writeFileSync(votesFile, JSON.stringify(votes, null, 2), 'utf8');
|
||
}
|
||
|
||
rpg.command('offer', async (ctx) => {
|
||
try {
|
||
const args = ctx.message.text.split(' ').slice(1);
|
||
const amount = parseInt(args[0]);
|
||
|
||
if (!amount || amount <= 0) {
|
||
return await ctx.reply('❌ Введите корректную сумму (например: /offer 5000)');
|
||
}
|
||
|
||
let votes = loadVotes();
|
||
votes[ctx.from.id] = amount; // Обновление суммы пользователя
|
||
saveVotes(votes);
|
||
|
||
return await ctx.reply(`✅ Вы предложили сумму: ${amount}₽`);
|
||
} catch (error) {
|
||
console.error(error);
|
||
return await ctx.reply('❌ Произошла ошибка при обработке команды.');
|
||
}
|
||
});
|
||
|
||
rpg.command('voteinfo', async (ctx) => {
|
||
if (ctx.from.id != 275416286) return;
|
||
|
||
let users = await UserModel.findAll({ attributes: ['telegram_id', 'username'] });
|
||
let userMap = Object.fromEntries(users.map(user => [user.telegram_id, user.username || `ID: ${user.telegram_id}`]));
|
||
|
||
let votes = loadVotes();
|
||
if (Object.keys(votes).length === 0) {
|
||
return await ctx.reply('🔹 Пока никто не предложил сумму.');
|
||
}
|
||
|
||
let total = Object.values(votes).reduce((sum, amount) => sum + amount, 0);
|
||
let details = Object.entries(votes)
|
||
.map(([id, amount]) => `👤 ${userMap[id] || `ID: ${id}`}: ${utils.spaces(amount)}₽`)
|
||
.join('\n');
|
||
|
||
await ctx.replyWithHTML(`📊 <b>Общий банк:</b> ${utils.spaces(total)}₽\n\n${details}`);
|
||
});
|
||
|
||
|
||
|
||
rpg.action('rpg_profile', async (ctx) => {
|
||
const telegramId = ctx.from.id;
|
||
|
||
// Ищем персонажа
|
||
const character = await CharacterModel.findOne({ where: { telegram_id: telegramId } });
|
||
const stolenCards = await StolenCardsModel.findAll({
|
||
where: { userId: character.telegram_id }
|
||
});
|
||
|
||
if (!character) {
|
||
return ctx.reply('Персонаж не найден. Создайте нового персонажа, чтобы начать игру!');
|
||
}
|
||
|
||
// Формируем профиль
|
||
const profile = `
|
||
🎭 Профиль персонажа
|
||
|
||
👤 Имя: ${character.name || 'Не указано'}
|
||
🏆 Уровень: ${character.level} (${character.exp}/${utils.spaces(expToUp[character.level])})
|
||
|
||
❤️ Здоровье: ${character.hp}/${character.max_hp}
|
||
🔥 Стамина: ${character.stamina}/${character.max_stamina}
|
||
|
||
💪 Сила (F): ${character.force}
|
||
🧠 Интеллект (I): ${character.intelligence}
|
||
🛡️ Устойчивость (R): ${character.resilience}
|
||
🔋 Выносливость (E): ${character.endurance}
|
||
➕ Свободные очки F.I.R.E: ${character.firePoints}
|
||
|
||
💰 Баланс: ${utils.spaces(character.dirtymoney)}Ð
|
||
🃏 Украденные карты: ${stolenCards.length}
|
||
`;
|
||
const buttons = [
|
||
[Markup.button.callback('💳 Карточки', 'view_cards')],
|
||
[Markup.button.callback('🎒 Инвентарь', 'inventory')],
|
||
];
|
||
|
||
if (character.firePoints >= 1) {
|
||
buttons.push([Markup.button.callback('➕ Распределить очки', 'fire_distribute')]);
|
||
}
|
||
buttons.push([Markup.button.callback('🔙 В меню', 'crime_menu')]);
|
||
// Отправляем сообщение
|
||
ctx.editMessageText(profile.trim(), Markup.inlineKeyboard(buttons));
|
||
});
|
||
|
||
// Основная команда для вывода кнопок распределения F.I.R.E.
|
||
rpg.action('fire_distribute', async (ctx) => {
|
||
// Получаем данные пользователя
|
||
const character = await CharacterModel.findOne({ where: { telegram_id: ctx.from.id } });
|
||
if (!character) {
|
||
return ctx.reply('Вы ещё не создали персонажа!');
|
||
}
|
||
|
||
// Проверяем, есть ли свободные очки
|
||
if (character.firePoints <= 0) {
|
||
return ctx.reply('У вас нет свободных очков для распределения.');
|
||
}
|
||
|
||
// Формируем текст сообщения
|
||
const message = `
|
||
🔥 Распределение очков F.I.R.E 🔥
|
||
|
||
У вас есть ${character.firePoints} свободных очков.
|
||
Выберите характеристику, чтобы увеличить её уровень:
|
||
|
||
💪 Сила (F): ${character.force}
|
||
🧠 Интеллект (I): ${character.intelligence}
|
||
🛡️ Устойчивость (R): ${character.resilience}
|
||
🔋 Выносливость (E): ${character.endurance}
|
||
`;
|
||
|
||
// Создаём кнопки для каждой характеристики
|
||
const keyboard = Markup.inlineKeyboard([
|
||
Markup.button.callback('💪', 'increase_force'),
|
||
Markup.button.callback('🧠', 'increase_intelligence'),
|
||
Markup.button.callback('🎯', 'increase_resilience'),
|
||
Markup.button.callback('💖', 'increase_endurance'),
|
||
], { columns: 2 });
|
||
logs(ctx, "Распределение очков F.I.R.E", {firePoints: character.firePoints, force: character.force, intelligence: character.intelligence, resilience: character.resilience, endurance: character.endurance});
|
||
// Отправляем сообщение с кнопками
|
||
await ctx.reply(message, keyboard);
|
||
});
|
||
|
||
// Обработчики для нажатий на кнопки
|
||
rpg.action(/^increase_(force|intelligence|resilience|endurance)$/, async (ctx) => {
|
||
const attribute = ctx.match[1]; // Получаем характеристику из callback_data
|
||
const character = await CharacterModel.findOne({ where: { telegram_id: ctx.from.id } });
|
||
if (!character) {
|
||
return ctx.answerCbQuery('Персонаж не найден.');
|
||
}
|
||
|
||
// Проверяем наличие свободных очков
|
||
if (character.firePoints <= 0) {
|
||
return ctx.answerCbQuery('У вас недостаточно очков!');
|
||
}
|
||
|
||
// Прокачиваем выбранную характеристику
|
||
switch (attribute) {
|
||
case 'force':
|
||
character.force++;
|
||
break;
|
||
case 'intelligence':
|
||
character.intelligence++;
|
||
break;
|
||
case 'resilience':
|
||
character.resilience++;
|
||
break;
|
||
case 'endurance':
|
||
character.endurance++;
|
||
break;
|
||
}
|
||
|
||
// Уменьшаем количество очков и сохраняем изменения
|
||
character.firePoints--;
|
||
logs(ctx, "Увеличение характеристики", { attribute: attribute, firePoints: character.firePoints, force: character.force, intelligence: character.intelligence, resilience: character.resilience, endurance: character.endurance });
|
||
await character.save();
|
||
|
||
// Отправляем обновлённое сообщение
|
||
const updatedMessage = `
|
||
🔥 Распределение очков F.I.R.E 🔥
|
||
|
||
У вас есть ${character.firePoints} свободных очков.
|
||
Выберите характеристику, чтобы увеличить её уровень:
|
||
|
||
💪 Сила (F): ${character.force}
|
||
🧠 Интеллект (I): ${character.intelligence}
|
||
🛡️ Устойчивость (R): ${character.resilience}
|
||
🔋 Выносливость (E): ${character.endurance}
|
||
`;
|
||
await ctx.editMessageText(updatedMessage, { reply_markup: ctx.update.callback_query.message.reply_markup });
|
||
|
||
ctx.answerCbQuery(`Вы увеличили ${attribute.toUpperCase()}!`);
|
||
});
|
||
|
||
|
||
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
||
|
||
// Хранилище для отслеживания пользователей, которые уже прошли ввод
|
||
const usersCrimeNet = new Set();
|
||
|
||
rpg.hears('CampFireGG.Crime', async (ctx) => {
|
||
const userId = ctx.from.id;
|
||
const character = await CharacterModel.findOne({ where: { telegram_id: userId } });
|
||
if (!usersCrimeNet.has(userId) && character.level === 1) {
|
||
// Если пользователь вводит команду впервые
|
||
usersCrimeNet.add(userId);
|
||
const username = ctx.from.username || 'User';
|
||
const randomServer = `secure.net-${Math.floor(Math.random() * 99 + 1)}.campfiregg.crime`;
|
||
const sshCommand = `ssh ${username}@${randomServer}`;
|
||
|
||
// Сообщение для симуляции
|
||
const lines = [
|
||
`${sshCommand}`,
|
||
`> Connecting to secure servers with RSA key...`,
|
||
`> Authentication in progress...`,
|
||
`> User "${username}" identified.`,
|
||
`> Accessing CampFireGG.Crime...`,
|
||
`> Connection established. Welcome, ${username}.`,
|
||
];
|
||
|
||
// Отправляем первое сообщение
|
||
let sentMessage = await ctx.reply('> Initializing CampFireGG.Crime...');
|
||
|
||
// Симуляция показа строки ssh блоками
|
||
let currentText = '';
|
||
let index = 0;
|
||
|
||
while (index < lines[0].length) {
|
||
const blockSize = Math.floor(Math.random() * 3) + 2; // Генерируем блок от 2 до 4 символов
|
||
currentText += lines[0].slice(index, index + blockSize); // Добавляем блок текста
|
||
index += blockSize; // Увеличиваем индекс
|
||
await ctx.telegram.editMessageText(sentMessage.chat.id, sentMessage.message_id, undefined, currentText);
|
||
await delay(100); // Задержка между блоками
|
||
}
|
||
|
||
// Последующие строки добавляются полностью, по одной
|
||
for (let i = 1; i < lines.length; i++) {
|
||
await delay(1500); // Задержка между строками
|
||
currentText += `\n${lines[i]}`;
|
||
await ctx.telegram.editMessageText(sentMessage.chat.id, sentMessage.message_id, undefined, currentText);
|
||
}
|
||
|
||
// Финальное меню после задержки
|
||
await delay(2000);
|
||
const buttons = [
|
||
[Markup.button.callback('💼 Задачи', 'crime_missions')],
|
||
[Markup.button.callback('📊 Профиль', 'rpg_profile')],
|
||
];
|
||
|
||
//buttons.push([Markup.button.callback('💳 Карточки', 'view_cards')]);
|
||
|
||
// Добавляем кнопку сообщения, если уровень персонажа равен 1
|
||
if (character && character.level === 1) {
|
||
buttons.push([Markup.button.callback('📩 (1) Сообщение', '1st_message')]);
|
||
}
|
||
|
||
await ctx.telegram.editMessageText(
|
||
sentMessage.chat.id,
|
||
sentMessage.message_id,
|
||
undefined,
|
||
`🚨 Добро пожаловать в CampFireGG.Crime 🚨\n\n`,
|
||
Markup.inlineKeyboard(buttons)
|
||
)
|
||
} else {
|
||
// Показываем только меню, если команда уже была введена
|
||
await ctx.reply(
|
||
`🚨 Добро пожаловать в CampFireGG.Crime 🚨\n\n`,
|
||
Markup.inlineKeyboard([
|
||
[Markup.button.callback('💼 Задачи', 'crime_missions')],
|
||
[Markup.button.callback('📊 Профиль', 'rpg_profile')],
|
||
[Markup.button.callback('💰 Магазин', 'shop')],
|
||
])
|
||
);
|
||
}
|
||
});
|
||
|
||
rpg.action('1st_message', async (ctx) => {
|
||
await ctx.answerCbQuery();
|
||
const message = `👤 Сообщение от неизвестного пользователя:
|
||
\`\`\`
|
||
"Добро пожаловать в CampFireGG.Crime. Если ты здесь, значит, у тебя есть вопросы. Забудь о них. Просто слушай."
|
||
|
||
🔥 F.I.R.E — твой путь к успеху:
|
||
"Каждый из нас что-то может. Кто-то силён, кто-то вынослив, а кто-то дохуя умный. В мире CampFireGG.Crime это называется F.I.R.E. Запомни эти буквы — от них зависит твоя жизнь здесь."
|
||
|
||
Force: Твоя мощь. Определяет, сколько импакта ты можешь внести. Чем выше, тем проще ликвидировать цель.
|
||
|
||
Intelligence: Твой интеллект. Чем ты умнее, тем больше шансов, что операция пройдет гладко. А ещё мозги ускоряют твое обучение.
|
||
|
||
Resilience: Твоя стойкость. Сколько времени тебе нужно, чтобы встать на ноги после провала? Чем выше этот параметр, тем быстрее ты восстанавливаешься.
|
||
|
||
Endurance: Твоя выносливость. Без неё ты не сможешь много работать, а значит, и много зарабатывать.
|
||
|
||
💰 Деньги? Грязные. Иначе здесь не бывает.
|
||
"Всё, что ты заработаешь здесь, будет грязным. Потратить можно только здесь, но есть способ отмыть деньги и перенести их на свой основной счёт. Всё зависит от твоего желания и возможностей."
|
||
|
||
💳 Карты? Да, их можно обналичить.
|
||
"Карточки — это всегда плюс. Взломал, достал, получил деньги. Всё просто. Главное — не профукай их."
|
||
|
||
🕶️ Последний совет:
|
||
"Каждое действие — это риск. Чем больше ты идёшь на риск, тем больше получаешь. Но если ошибёшься, можешь встрять. Так что думай."
|
||
|
||
"Добро пожаловать. Удачи тебе, новобранец."
|
||
\`\`\`
|
||
`;
|
||
|
||
await ctx.replyWithMarkdownV2(message, { disable_web_page_preview: true });
|
||
});
|
||
|
||
rpg.action('view_cards', async (ctx) => {
|
||
try {
|
||
// Получаем пользователя по Telegram ID
|
||
const character = await CharacterModel.findByPk(ctx.from.id);
|
||
|
||
if (!character) {
|
||
return ctx.reply('Персонаж не найден!');
|
||
}
|
||
|
||
// Получаем все украденные карточки для этого пользователя
|
||
const stolenCards = await StolenCardsModel.findAll({
|
||
where: { userId: character.telegram_id }
|
||
});
|
||
|
||
if (stolenCards.length === 0) {
|
||
return ctx.reply('У вас нет украденных карточек.');
|
||
}
|
||
|
||
const buttons = [];
|
||
let message = 'Ваши украденные карты:\n\n';
|
||
|
||
stolenCards.forEach(card => {
|
||
const lastFourDigits = card.cardNumber.slice(-4); // Берем последние 4 цифры карточки
|
||
message += `🔒 Карта: *${lastFourDigits}\n`;
|
||
|
||
// Генерация кнопок для брутфорса и покупки за лимит
|
||
buttons.push([
|
||
Markup.button.callback(`💳 *${lastFourDigits}`, `brutecard_${card.id}`),
|
||
//Markup.button.callback(`💰 *${lastFourDigits}`, `limitbuy_${card.id}`)
|
||
Markup.button.callback('🔙 В меню', 'crime_menu'),
|
||
]);
|
||
});
|
||
|
||
// Отправляем сообщение с кнопками
|
||
await ctx.reply(message, Markup.inlineKeyboard(buttons));
|
||
|
||
} catch (error) {
|
||
console.error('Ошибка при выводе карточек:', error);
|
||
await ctx.reply('Произошла ошибка при получении ваших карточек.');
|
||
}
|
||
});
|
||
|
||
rpg.action(/limitbuy_/, async (ctx) => {
|
||
try {
|
||
const cardId = ctx.match.input.split('_')[1]; // ID карточки
|
||
const card = await StolenCardsModel.findByPk(cardId); // Получаем карточку
|
||
|
||
if (!card) {
|
||
return ctx.answerCbQuery('Карточка не найдена.');
|
||
}
|
||
|
||
const character = await CharacterModel.findByPk(ctx.from.id); // Получаем персонажа
|
||
if (!character) {
|
||
return ctx.answerCbQuery('Персонаж не найден.');
|
||
}
|
||
|
||
// Получаем 10 случайных предметов из базы
|
||
const items = await ItemsModel.findAll({ limit: 10 });
|
||
|
||
// Если у пользователя нет записей о попытках, создаем их
|
||
if (!attempts[ctx.from.id]) {
|
||
attempts[ctx.from.id] = 0;
|
||
}
|
||
|
||
// Генерация кнопок с предметами
|
||
const buttons = [];
|
||
items.forEach(item => {
|
||
const buttonText = `${item.name} - ¥${item.price}`;
|
||
buttons.push([Markup.button.callback(buttonText, `purchase_${item.id}_${card.id}`)]);
|
||
});
|
||
|
||
await ctx.reply(
|
||
'Выберите товар для покупки:\nПопробуйте угадать лимит карты.\n\n',
|
||
Markup.inlineKeyboard(buttons)
|
||
);
|
||
} catch (error) {
|
||
console.error('Ошибка при обработке покупки через лимит:', error);
|
||
}
|
||
});
|
||
|
||
rpg.action(/purchase_/, async (ctx) => {
|
||
try {
|
||
const [_, itemId, cardId] = ctx.match.input.split('_');
|
||
const item = await ItemsModel.findByPk(itemId);
|
||
const card = await StolenCardsModel.findByPk(cardId);
|
||
const character = await CharacterModel.findByPk(ctx.from.id);
|
||
|
||
if (!item || !card || !character) {
|
||
return ctx.answerCbQuery('Ошибка при обработке покупки.');
|
||
}
|
||
|
||
|
||
// Проверяем, не исчерпаны ли попытки
|
||
if (attempts[ctx.from.id] >= 3) {
|
||
// Сбрасываем попытки и уничтожаем карточку
|
||
delete attempts[ctx.from.id];
|
||
await StolenCardsModel.destroy({ where: { id: cardId } });
|
||
|
||
return ctx.editMessageText(
|
||
'❌ Все попытки исчерпаны! Карточка стала недействительной.'
|
||
);
|
||
}
|
||
|
||
// Если баланс карты меньше, чем цена товара, увеличиваем количество попыток
|
||
if (card.balance < item.price) {
|
||
attempts[ctx.from.id]++;
|
||
await ctx.answerCbQuery(`Недостаточно средств! Попыток осталось: ${3 - ctx.session.attempts}`);
|
||
return ctx.editMessageText(`Вы выбрали товар за ¥${item.price}, но у вас недостаточно средств.`);
|
||
}
|
||
|
||
// Если баланс карты достаточно для покупки, добавляем предмет в инвентарь
|
||
character.InventoryModel.push(item.id);
|
||
await character.save();
|
||
|
||
// Если товар электронный, начисляем процент от стоимости на счет
|
||
if (item.type === 'digital') {
|
||
const profit = Math.round(item.price * 0.2); // 20% от стоимости товара
|
||
character.dirtymoney += profit;
|
||
await character.save();
|
||
await ctx.answerCbQuery(`Вы приобрели ${item.name} и перепродали за ¥${profit}.`);
|
||
} else {
|
||
await ctx.answerCbQuery(`Вы купили ${item.name}.`);
|
||
}
|
||
|
||
// Блокируем карту после покупки
|
||
card.destroy({ where: { id: cardId } });
|
||
await card.save();
|
||
|
||
// Оповещаем пользователя
|
||
await ctx.editMessageText(`${item.name} успешно куплен! Карта заблокирована.`);
|
||
} catch (error) {
|
||
console.error('Ошибка при обработке покупки:', error);
|
||
}
|
||
});
|
||
|
||
rpg.action(/purchase_fail_/, async (ctx) => {
|
||
try {
|
||
const [_, itemId, cardId] = ctx.match.input.split('_');
|
||
const item = await ItemsModel.findByPk(itemId);
|
||
const card = await StolenCardsModel.findByPk(cardId);
|
||
|
||
if (!item || !card) {
|
||
return ctx.answerCbQuery('Ошибка при проверке предмета или карты.');
|
||
}
|
||
|
||
await ctx.answerCbQuery('Недостаточно средств для покупки.');
|
||
} catch (error) {
|
||
console.error('Ошибка при обработке неудачной покупки:', error);
|
||
}
|
||
});
|
||
|
||
|
||
// Обработка попытки взлома карточки
|
||
rpg.action(/brutecard_/, async (ctx) => {
|
||
const cardId = ctx.match.input.split('_')[1]; // Получаем ID карточки из callback_data
|
||
const card = await StolenCardsModel.findByPk(cardId); // Ищем карточку в базе данных
|
||
const inventory = await InventoryModel.findAll({ where: { telegram_id: ctx.from.id } }); // Загружаем предметы из инвентаря
|
||
|
||
if (!card) {
|
||
return ctx.answerCbQuery('Карточка не найдена.');
|
||
}
|
||
|
||
// Проверяем наличие "Эмулятора картридера" в инвентаре
|
||
const emulator = inventory.find((item) => item.text_id === 'cardreader_emulator');
|
||
if (!emulator) {
|
||
return ctx.reply('Для запуска брутфорса вам нужен "Эмулятор картридера".');
|
||
}
|
||
// Удаляем "Эмулятор картридера" из инвентаря
|
||
await InventoryModel.destroy({ where: { id: emulator.id } });
|
||
|
||
const buttonsCount = 10; // Количество кнопок
|
||
const winButton = utils.rand(1, buttonsCount); // Выбираем случайную кнопку для успешного пина
|
||
const maxAttempts = 3; // Максимальное количество попыток
|
||
|
||
// Если у пользователя нет записей о попытках, создаем их
|
||
if (!attempts[ctx.from.id]) {
|
||
attempts[ctx.from.id] = 0;
|
||
}
|
||
|
||
// Генерация кнопок
|
||
const buttons = [];
|
||
let fakePin = 1234
|
||
for (let i = 1; i <= buttonsCount; i++) {
|
||
if (i === winButton) {
|
||
// Если это победная кнопка (правильный ПИН)
|
||
buttons.push({ text: `${card.pin}`, callback_data: `brute_success_${cardId}` });
|
||
} else {
|
||
fakePin = faker.finance.pin()
|
||
// Если это неудачная кнопка (случайный ПИН)
|
||
buttons.push({ text: `${fakePin}`, callback_data: `brute_fail_${cardId}_${i}` });
|
||
}
|
||
}
|
||
|
||
// Разбиваем кнопки на строки по 5 кнопок
|
||
const rows = [];
|
||
while (buttons.length > 0) {
|
||
rows.push(buttons.splice(0, 5)); // Разбиваем на подмассивы по 5 кнопок
|
||
}
|
||
|
||
// Сортируем кнопки случайным образом
|
||
const keyboard = Markup.inlineKeyboard(
|
||
rows.sort(() => Math.random() - 0.5).map((row) => row) // Сортируем строки кнопок случайным образом
|
||
);
|
||
logs(ctx, "Брутфорс карты", {card: card, keyboard: keyboard, emulator: emulator});
|
||
// Отправляем сообщение с клавиатурой
|
||
await ctx.reply(
|
||
`💳 Брутфорс карты\n
|
||
━━━━━━━━━━━━━━━━━━━━
|
||
${card.bankName.toUpperCase()}
|
||
━━━━━━━━━━━━━━━━━━━━
|
||
Номер: ${card.cardNumber}
|
||
CVV: ${card.cvv}
|
||
Владелец: ${card.holderName.toUpperCase()}
|
||
Срок действия: ${card.expiresDate}
|
||
━━━━━━━━━━━━━━━━━━━━
|
||
|
||
Попробуйте подобрать правильный ПИН-код. У вас есть ${maxAttempts - attempts[ctx.from.id]} попыток.
|
||
|
||
"Эмулятор картридера" был использован.`,
|
||
keyboard
|
||
);
|
||
|
||
});
|
||
|
||
|
||
// Обработка неудачной попытки взлома
|
||
rpg.action(/brute_fail_/, async (ctx) => {
|
||
const cardId = ctx.match.input.split('_')[2];
|
||
const maxAttempts = 3; // Максимальное количество попыток
|
||
|
||
// Увеличиваем количество попыток
|
||
attempts[ctx.from.id]++;
|
||
|
||
// Проверяем, не исчерпаны ли попытки
|
||
if (attempts[ctx.from.id] >= maxAttempts) {
|
||
// Сбрасываем попытки и уничтожаем карточку
|
||
delete attempts[ctx.from.id];
|
||
await StolenCardsModel.destroy({ where: { id: cardId } });
|
||
|
||
return ctx.editMessageText(
|
||
'❌ Все попытки исчерпаны! Карточка стала недействительной.'
|
||
);
|
||
}
|
||
|
||
// Ответ на нажатие кнопки
|
||
ctx.answerCbQuery(`❌ Неправильный ПИН. Попыток осталось: ${maxAttempts - attempts[ctx.from.id]}`);
|
||
|
||
// Обновляем клавиатуру
|
||
const card = await StolenCardsModel.findByPk(cardId);
|
||
const buttonsCount = 10;
|
||
const winButton = utils.rand(1, buttonsCount);
|
||
const buttons = [];
|
||
let fakePin = 1234
|
||
|
||
for (let i = 1; i <= buttonsCount; i++) {
|
||
fakePin = faker.finance.pin()
|
||
if (i === winButton) {
|
||
buttons.push({ text: `${card.pin}`, callback_data: `brute_success_${cardId}` });
|
||
} else {
|
||
buttons.push({ text: `${fakePin}`, callback_data: `brute_fail_${cardId}_${i}` });
|
||
}
|
||
}
|
||
|
||
const rows = [];
|
||
while (buttons.length > 0) {
|
||
rows.push(buttons.splice(0, 5));
|
||
}
|
||
|
||
const keyboard = Markup.inlineKeyboard(
|
||
rows.sort(() => Math.random() - 0.5).map((row) => row)
|
||
);
|
||
logs(ctx, "Брутфорс карты (Fail)", {card: card, keyboard: keyboard});
|
||
|
||
// Обновляем сообщение с клавиатурой
|
||
await ctx.editMessageText(
|
||
`💳 Брутфорс карты\n
|
||
━━━━━━━━━━━━━━━━━━━━
|
||
${card.bankName.toUpperCase()}
|
||
━━━━━━━━━━━━━━━━━━━━
|
||
Номер: ${card.cardNumber}
|
||
CVV: ${card.cvv}
|
||
Владелец: ${card.holderName.toUpperCase()}
|
||
Срок действия: ${card.expiresDate}
|
||
━━━━━━━━━━━━━━━━━━━━
|
||
|
||
Попробуйте подобрать правильный ПИН-код. У вас есть ${maxAttempts - attempts[ctx.from.id]} попыток.`,
|
||
keyboard
|
||
);
|
||
});
|
||
|
||
// Обработка успешной попытки взлома
|
||
rpg.action(/brute_success_/, async (ctx) => {
|
||
|
||
const cardId = ctx.match.input.split('_')[2];
|
||
const card = await StolenCardsModel.findByPk(cardId);
|
||
if (!card) {
|
||
return ctx.answerCbQuery('Карточка не найдена.');
|
||
}
|
||
delete attempts[ctx.from.id];
|
||
const amount = card.balance; // Сумма денег за карточку
|
||
const character = await CharacterModel.findByPk(ctx.from.id);
|
||
logs(ctx, "Брутфорс карты (Success)", {card: card, amount: amount});
|
||
// Увеличиваем грязные деньги пользователя
|
||
character.dirtymoney += amount;
|
||
await character.save();
|
||
|
||
// Удаляем карточку
|
||
await card.destroy();
|
||
|
||
// Отправляем сообщение о выигрыше
|
||
await ctx.editMessageText(
|
||
`💳 Брутфорс карты\n
|
||
━━━━━━━━━━━━━━━━━━━━
|
||
${card.bankName.toUpperCase()}
|
||
━━━━━━━━━━━━━━━━━━━━
|
||
Номер: ${card.cardNumber}
|
||
CVV: ${card.cvv}
|
||
Владелец: ${card.holderName.toUpperCase()}
|
||
Срок действия: ${card.expiresDate}
|
||
━━━━━━━━━━━━━━━━━━━━
|
||
|
||
✅ Взлом успешен! Вы получили Ð${amount}.`
|
||
);
|
||
});
|
||
|
||
|
||
|
||
|
||
// Обработчики кнопок
|
||
rpg.action('crime_missions', async (ctx) => {
|
||
await ctx.answerCbQuery();
|
||
await ctx.editMessageText(`💼 **Задачи:**\n Для карманных краж тебе не нужно ничего, главное не попадись.\n А вот после ограбления магазина в переулке не спрячешься, без тачки на миссию нельзя.`,
|
||
Markup.inlineKeyboard([
|
||
[{ text: 'Карманные кражи [1 lvl.]', callback_data: `POCKET_ACTION` }],
|
||
[{ text: 'Магазин [5 lvl.]', callback_data: `SHOP_ACTION` }],
|
||
//[{text: 'Банкомат', callback_data: `WIP`}],
|
||
// [{text: 'Банковское отделение', callback_data: `WIP`}],
|
||
//[{text: 'Угон', callback_data: `WIP`}],
|
||
// [{text: 'Ювелирка', callback_data: `WIP`}],
|
||
//[{text: 'Банк', callback_data: `WIP`}],
|
||
[{ text: '🔙 В меню', callback_data: `crime_menu` }]
|
||
]),
|
||
);
|
||
});
|
||
|
||
rpg.action(`POCKET_ACTION`, async (ctx) => {
|
||
let user = await UserModel.findByPk(ctx.from.id)
|
||
let character = await CharacterModel.findByPk(ctx.from.id);
|
||
let pocketsteal = character.pocketstealcd
|
||
if (character.level < 1) return ctx.editMessageText('Доступно с 1 уровня!')
|
||
if (character.stamina < 10) return ctx.editMessageText('Вы устали!')
|
||
let cooldown = utils.setCooldown(character, 3600, pocketsteal)
|
||
if (character.pocketstealcd > cooldown.currentTime) return ctx.editMessageText(`📛 Данное действие будет доступно через ${cooldown.timeLeftInMinutes} мин.`);
|
||
character.pocketstealcd = cooldown.endTime
|
||
character.stamina -= 10
|
||
logs(ctx, "Карманная кража (Start)", { cdBefore: pocketsteal, cdAfter: character.pocketstealcd, stamina: character.stamina });
|
||
character.save()
|
||
ctx.editMessageText('Выберите объект', Markup.inlineKeyboard([
|
||
[
|
||
{ text: 'Карман', callback_data: `POCKET_TARGET` },
|
||
{ text: 'Бумажник', callback_data: `POCKET_WALLET` },
|
||
{ text: 'Сумка', callback_data: `POCKET_BAG` }
|
||
]
|
||
]))
|
||
});
|
||
|
||
rpg.action(`POCKET_TARGET`, async (ctx) => {
|
||
ctx.editMessageText('В кармане обнаружено', Markup.inlineKeyboard([
|
||
[
|
||
{ text: 'Деньги', callback_data: `MONEY_IN_POCKET` },
|
||
{ text: 'Телефон', callback_data: `PHONE` }
|
||
]
|
||
]))
|
||
});
|
||
|
||
rpg.action(`MONEY_IN_POCKET`, async (ctx) => {
|
||
// Получаем пользователя и его персонажа
|
||
//let user = await UserModel.findByPk(ctx.from.id);
|
||
let character = await CharacterModel.findByPk(ctx.from.id);
|
||
|
||
if (!character) {
|
||
return ctx.editMessageText('У вас нет персонажа. Создайте его перед началом.');
|
||
}
|
||
|
||
// Расчёт шанса на успешную кражу
|
||
let baseChance = 40; // Базовый шанс
|
||
let chance = baseChance + character.intelligence * 2; // Увеличиваем шанс на 2% за каждый пункт "Разума".
|
||
let randomRoll = utils.rand(0, 100); // Случайное число от 0 до 100
|
||
if (randomRoll > chance) {
|
||
Exp(ctx, character, 1)
|
||
return ctx.editMessageText('Вы были замечены во время кражи.');
|
||
}
|
||
// Успешная кража
|
||
let moneyIn = utils.rand(5, 1000);
|
||
Exp(ctx, character, character.intelligence + 3)
|
||
character.dirtymoney += moneyIn;
|
||
logs(ctx, "Карманная кража (Кошелек)", {baseChance: baseChance, chance: chance, randomRoll: randomRoll, moneyIn: moneyIn});
|
||
await character.save();
|
||
return ctx.editMessageText(`Вы успешно украли Ð${utils.spaces(moneyIn)} из кармана.`);
|
||
});
|
||
|
||
|
||
rpg.action(`PHONE`, async (ctx) => {
|
||
let character = await CharacterModel.findByPk(ctx.from.id);
|
||
//let user = await UserModel.findByPk(ctx.from.id)
|
||
let property = await PropertyModel.findByPk(ctx.from.id);
|
||
// Расчёт шанса на успешную кражу
|
||
let baseChance = 20; // Базовый шанс
|
||
let chance = baseChance + character.intelligence * 2; // Увеличиваем шанс на 2% за каждый пункт "Разума".
|
||
let randomRoll = utils.rand(0, 100); // Случайное число от 0 до 100
|
||
if (chance < randomRoll) {
|
||
Exp(ctx, character, 1)
|
||
return ctx.editMessageText('Вы были замечены во время кражи.')
|
||
}
|
||
let randPhone = utils.rand(1, 10)
|
||
if (property.mobile.name) {
|
||
let dirtyMoney = Math.round(phones[randPhone].price / 100 * 70)
|
||
Exp(ctx, character, character.intelligence + 5)
|
||
character.dirtymoney += dirtyMoney
|
||
return await ctx.reply(`Вы сбыли украденный ${phones[randPhone].name} за Ð${utils.spaces(dirtyMoney)}`)
|
||
}
|
||
property.mobile = phones[randPhone]
|
||
logs(ctx, "Карманная кража (Телефон)", {baseChance: baseChance, chance: chance, randomRoll: randomRoll, phone: phones[randPhone]});
|
||
await character.save()
|
||
await property.save()
|
||
return ctx.editMessageText(`Вы успешно украли ${phones[randPhone].name} из кармана.`)
|
||
});
|
||
|
||
rpg.action(`POCKET_WALLET`, async (ctx) => {
|
||
ctx.editMessageText('В бумажнике обнаружено', Markup.inlineKeyboard([
|
||
[
|
||
{ text: 'Деньги', callback_data: `MONEY_IN_WALLET` },
|
||
{ text: 'Карточка', callback_data: `CARD_IN_WALLET` }
|
||
]
|
||
]))
|
||
});
|
||
|
||
rpg.action(`MONEY_IN_WALLET`, async (ctx) => {
|
||
//let user = await UserModel.findByPk(ctx.from.id)
|
||
let character = await CharacterModel.findByPk(ctx.from.id);
|
||
// Расчёт шанса на успешную кражу
|
||
let baseChance = 35; // Базовый шанс
|
||
let chance = baseChance + character.intelligence * 2; // Увеличиваем шанс на 2% за каждый пункт "Разума".
|
||
let randomRoll = utils.rand(0, 100); // Случайное число от 0 до 100
|
||
if (chance < randomRoll) {
|
||
Exp(ctx, character, 1)
|
||
return ctx.editMessageText('Вы были замечены во время кражи.')
|
||
}
|
||
let moneyIn = utils.rand(1000, 10000)
|
||
Exp(ctx, character, character.intelligence + 5)
|
||
character.dirtymoney += moneyIn
|
||
logs(ctx, "Карманная кража (Бумажник)", {baseChance: baseChance, chance: chance, randomRoll: randomRoll, moneyIn: moneyIn});
|
||
character.save()
|
||
return ctx.editMessageText(`Вы успешно украли Ð${utils.spaces(moneyIn)} из бумажника.`)
|
||
});
|
||
|
||
rpg.action(`CARD_IN_WALLET`, async (ctx) => {
|
||
//let user = await UserModel.findByPk(ctx.from.id)
|
||
let character = await CharacterModel.findByPk(ctx.from.id);
|
||
let baseChance = 20; // Базовый шанс
|
||
let chance = baseChance + character.intelligence * 2; // Увеличиваем шанс на 2% за каждый пункт "Разума".
|
||
let randomRoll = utils.rand(0, 100); // Случайное число от 0 до 100
|
||
if (chance < randomRoll) {
|
||
Exp(ctx, character, 2)
|
||
return ctx.editMessageText('Вы были замечены во время кражи.')
|
||
}
|
||
Exp(ctx, character, character.intelligence + 5)
|
||
let newCard = generateCard(ctx.from.id)
|
||
logs(ctx, "Карманная кража (Карта)", {baseChance: baseChance, chance: chance, randomRoll: randomRoll, card: newCard});
|
||
character.save()
|
||
return ctx.editMessageText(`Вы успешно украли 💳 из бумажника.`)
|
||
});
|
||
|
||
rpg.action(`POCKET_BAG`, async (ctx) => {
|
||
//let user = await UserModel.findByPk(ctx.from.id)
|
||
let character = await CharacterModel.findByPk(ctx.from.id);
|
||
let baseChance = 20; // Базовый шанс
|
||
let chance = baseChance + character.intelligence * 2; // Увеличиваем шанс на 2% за каждый пункт "Разума".
|
||
let randomRoll = utils.rand(0, 100); // Случайное число от 0 до 100
|
||
if (chance < randomRoll) {
|
||
Exp(ctx, character, 1)
|
||
return ctx.editMessageText('Вы были замечены во время кражи.')
|
||
}
|
||
let times = utils.rand(2, 20)
|
||
let moneyIn = 0
|
||
let text = ``
|
||
let values = 0
|
||
|
||
for (i = 1; i <= times; i++) {
|
||
randomize = utils.rand(1, 100)
|
||
switch (randomize) {
|
||
case 2:
|
||
values = utils.rand(10000, 50000)
|
||
moneyIn += values
|
||
text += `+ Ð${utils.spaces(values)}\n`
|
||
break;
|
||
case 7:
|
||
values = utils.rand(10000, 100000)
|
||
moneyIn += values
|
||
text += `+ Ð${utils.spaces(values)}\n`
|
||
break;
|
||
default:
|
||
values = utils.rand(100, 3000)
|
||
moneyIn += values
|
||
text += `+ Ð${utils.spaces(values)}\n`
|
||
break;
|
||
}
|
||
}
|
||
Exp(ctx, character, character.intelligence + 5)
|
||
character.dirtymoney += moneyIn
|
||
logs(ctx, "Карманная кража (Сумка)", {baseChance: baseChance, chance: chance, randomRoll: randomRoll, moneyIn: moneyIn});
|
||
character.save()
|
||
return ctx.editMessageText(`Вы успешно украли сумку и сбыли все ценности из нее:\n${text}\nОбщий куш: Ð${utils.spaces(moneyIn)}`)
|
||
});
|
||
|
||
rpg.action('crime_menu', async (ctx) => {
|
||
await ctx.answerCbQuery();
|
||
await ctx.editMessageText(
|
||
`💻 CampFireGG.Crime Menu`,
|
||
Markup.inlineKeyboard([
|
||
[Markup.button.callback('💼 Задачи', 'crime_missions')],
|
||
[Markup.button.callback('📊 Профиль', 'rpg_profile')],
|
||
[Markup.button.callback('💰 Магазин', 'shop')],
|
||
])
|
||
);
|
||
});
|
||
|
||
rpg.action('SHOP_ACTION', async (ctx) => {
|
||
let user = await UserModel.findByPk(ctx.from.id)
|
||
let character = await CharacterModel.findByPk(ctx.from.id);
|
||
let property = await PropertyModel.findByPk(ctx.from.id);
|
||
if (character.level < 5) return ctx.editMessageText('Доступно с 5 уровня!')
|
||
if (property.car1 == 0) return ctx.editMessageText('Для данного задания нужна тачка.')
|
||
if (character.stamina < 25) return ctx.editMessageText('Вы устали!')
|
||
let shoprobcd = character.shoprobcd
|
||
let cooldown = utils.setCooldown(character, 3600, shoprobcd)
|
||
if (character.shoprobcd > cooldown.currentTime) return ctx.editMessageText(`📛 Данное действие будет доступно через ${cooldown.timeLeftInMinutes} мин.`);
|
||
character.shoprobcd = cooldown.endTime
|
||
character.stamina -= 25
|
||
logs(ctx, "Ограбление магазина (Start)", {cdBefore: shoprobcd, cdAfter: character.shoprobcd, stamina: character.stamina});
|
||
character.save()
|
||
return ctx.editMessageText('Стадии:', Markup.inlineKeyboard([
|
||
[{ text: 'Взлом кассы', callback_data: `SHOP_CASH_BREAK` }],
|
||
[{ text: 'Разбить кассу', callback_data: `SHOP_CASH_SMASH` }]
|
||
]))
|
||
});
|
||
|
||
rpg.action(`SHOP_CASH_BREAK`, async (ctx) => {
|
||
let character = await CharacterModel.findByPk(ctx.from.id);
|
||
let baseChance = 20; // Базовый шанс
|
||
let chance = baseChance + character.intelligence * 2; // Увеличиваем шанс на 2% за каждый пункт "Разума".
|
||
let randomRoll = utils.rand(0, 100); // Случайное число от 0 до 100
|
||
let cashIn = utils.rand(1000, 10000)
|
||
let timer = 1000
|
||
if (chance < randomRoll) {
|
||
const keyboard = generateKeyboard();
|
||
ctx.deleteMessage()
|
||
logs(ctx, "Ограбление магазина (Взлом кассы ручной)", {baseChance: baseChance, chance: chance, randomRoll: randomRoll, keyboard: keyboard, cashIn: cashIn});
|
||
return ctx.reply('Касса закрыта, вы начали взлом замка:', keyboard);
|
||
//ctx.editMessageText('Вы начали взлом кассы.');
|
||
//return ctx.scene.enter('LOCKPICK')
|
||
}
|
||
for (i = 0; i < utils.rand(character.endurance, character.endurance + 15); i++) {
|
||
setTimeout(() => {
|
||
cashIn += utils.rand(1000, 10000)
|
||
ctx.editMessageText(`⏏️ Вы достали из кассы: Ð${utils.spaces(cashIn)}`)
|
||
}, timer)
|
||
timer += 500
|
||
}
|
||
setTimeout(() => {
|
||
Exp(ctx, character, character.intelligence + 10)
|
||
character.dirtymoney += cashIn
|
||
character.save()
|
||
return ctx.editMessageText(`Вы достали из кассы Ð${utils.spaces(cashIn)}, пора валить.`, Markup.inlineKeyboard([
|
||
[{ text: 'Завершить ограбление', callback_data: `SHOP_END` }]
|
||
]))
|
||
}, timer + 300)
|
||
logs(ctx, "Ограбление магазина (Взлом кассы быстро)", {baseChance: baseChance, chance: chance, randomRoll: randomRoll, cashIn: cashIn});
|
||
});
|
||
|
||
rpg.action(`SHOP_CASH_SMASH`, async (ctx) => {
|
||
let character = await CharacterModel.findByPk(ctx.from.id);
|
||
let baseChance = 20; // Базовый шанс
|
||
let chance = baseChance + character.intelligence * 2; // Увеличиваем шанс на 2% за каждый пункт "Разума".
|
||
let randomRoll = utils.rand(0, 100); // Случайное число от 0 до 100
|
||
let cashIn = utils.rand(1000, 10000)
|
||
let timer = 1000
|
||
if (chance < randomRoll) {
|
||
Exp(ctx, character, 1)
|
||
return ctx.editMessageText('Вы разбили кассовый аппарат, и сработала сигнализация. Вы сбежали.')
|
||
}
|
||
ctx.editMessageText('Вы разбили кассовый аппарат, и сработала сигнализация.')
|
||
for (i = 0; i < utils.rand(character.endurance, character.endurance + 8); i++) {
|
||
setTimeout(() => {
|
||
cashIn += utils.rand(500, 5000)
|
||
ctx.editMessageText(`⏏️ Вы в спешке достали из кассы: Ð${utils.spaces(cashIn)}`)
|
||
}, timer)
|
||
timer += 500
|
||
}
|
||
setTimeout(() => {
|
||
Exp(ctx, character, character.intelligence + 10)
|
||
character.dirtymoney += cashIn
|
||
character.save()
|
||
return ctx.editMessageText(`Вы в спешке достали из кассы Ð${utils.spaces(cashIn)}, пора валить.`, Markup.inlineKeyboard([
|
||
[{ text: 'Завершить ограбление', callback_data: `SHOP_END` }]
|
||
]))
|
||
}, timer + 300)
|
||
logs(ctx, "Ограбление магазина (Разбитие кассы)", {baseChance: baseChance, chance: chance, randomRoll: randomRoll, cashIn: cashIn});
|
||
});
|
||
|
||
rpg.action(`SHOP_CASH_BREAK_SUCCESS`, async (ctx) => {
|
||
let user = await UserModel.findByPk(ctx.from.id)
|
||
let character = await CharacterModel.findByPk(ctx.from.id);
|
||
delete attempts[ctx.from.id];
|
||
let cashIn = utils.rand(1000, 10000)
|
||
let timer = 100
|
||
for (i = 0; i < utils.rand(character.endurance, character.endurance + 15); i++) {
|
||
setTimeout(() => {
|
||
cashIn += utils.rand(3000, 10000)
|
||
ctx.editMessageText(`⏏️ Вы достали из кассы: Ð${utils.spaces(cashIn)}`)
|
||
}, timer)
|
||
timer += 500
|
||
}
|
||
setTimeout(() => {
|
||
Exp(ctx, character, character.intelligence + 15)
|
||
character.dirtymoney += cashIn
|
||
character.save()
|
||
return ctx.editMessageText(`Вы достали из кассы Ð${utils.spaces(cashIn)}, пора валить.`, Markup.inlineKeyboard([
|
||
[{ text: 'Завершить ограбление', callback_data: `SHOP_END` }]
|
||
]))
|
||
}, timer + 300)
|
||
logs(ctx, "Ограбление магазина (Взлом кассы)", {cashIn: cashIn});
|
||
});
|
||
|
||
rpg.action(`SHOP_END`, async (ctx) => {
|
||
return ctx.editMessageText('Ограбление завершено!')
|
||
});
|
||
|
||
const attempts = {}; // Храним количество попыток взлома для каждого пользователя
|
||
|
||
rpg.action(/lock_*/, async (ctx) => {
|
||
const userId = ctx.from.id;
|
||
const buttonId = ctx.update.callback_query.data;
|
||
|
||
// Если пользователя нет в списке попыток, добавляем его
|
||
if (!attempts[userId]) {
|
||
attempts[userId] = 0;
|
||
}
|
||
|
||
// Увеличиваем количество попыток
|
||
attempts[userId] += 1;
|
||
|
||
// Проверяем, не исчерпаны ли попытки
|
||
if (attempts[userId] >= 5) {
|
||
delete attempts[userId]; // Сбрасываем попытки после провала
|
||
logs(ctx, "Взлом замка (Full Fail)", {attempts: attempts[userId]});
|
||
return ctx.editMessageText('Взлом провалился. Замок остается нетронутым.');
|
||
}
|
||
logs(ctx, "Взлом замка (Fail)", {attempts: attempts[userId]});
|
||
// Ответ на нажатие кнопки
|
||
ctx.answerCbQuery(`Штифт не сдвинулся. Попыток осталось: ${5 - attempts[userId]}`);
|
||
removeButton(ctx, buttonId);
|
||
});
|
||
|
||
|
||
//Функция для отображения читаемого типа предмета "implant" "consumable" "accessory" "weapon" "armor" "helmet" "boots" и другие недостающие
|
||
function getItemType(type) {
|
||
switch (type) {
|
||
case 'implant':
|
||
return 'Имплант';
|
||
case 'consumable':
|
||
return 'Расходник';
|
||
case 'accessory':
|
||
return 'Аксессуар';
|
||
case 'weapon':
|
||
return 'Оружие';
|
||
case 'armor':
|
||
return 'Броня';
|
||
case 'helmet':
|
||
return 'Шлем';
|
||
case 'boots':
|
||
return 'Обувь';
|
||
default:
|
||
return type;
|
||
}
|
||
}
|
||
|
||
rpg.action('inventory', async (ctx) => {
|
||
try {
|
||
const character = await CharacterModel.findByPk(ctx.from.id);
|
||
|
||
if (!character) {
|
||
return ctx.reply('Персонаж не найден!');
|
||
}
|
||
|
||
// Загружаем предметы из инвентаря
|
||
const inventory = await InventoryModel.findAll({ where: { telegram_id: ctx.from.id } });
|
||
|
||
if (inventory.length === 0) {
|
||
return ctx.reply('Ваш инвентарь пуст.');
|
||
}
|
||
|
||
let message = '*Ваш инвентарь:*\n\n';
|
||
|
||
// Группируем предметы по их состоянию (снаряженные/обычные)
|
||
const equippedItems = inventory.filter((item) => item.equipped);
|
||
const unequippedItems = inventory.filter((item) => !item.equipped);
|
||
|
||
if (equippedItems.length > 0) {
|
||
message += '🛡️ Снаряженные предметы:\n';
|
||
equippedItems.forEach((item) => {
|
||
message += `- ${item.name} (${getItemType(item.type)})\n`;
|
||
});
|
||
message += '\n';
|
||
}
|
||
|
||
if (unequippedItems.length > 0) {
|
||
message += '🎒 Предметы в инвентаре:\n';
|
||
unequippedItems.forEach((item) => {
|
||
message += `- ${item.name} (${getItemType(item.type)})\n`;
|
||
});
|
||
message += '\n';
|
||
}
|
||
|
||
// Кнопки для взаимодействия
|
||
const buttons = [
|
||
...unequippedItems.map((item) =>
|
||
Markup.button.callback(`🔎 ${item.name}`, `view_item_${item.id}`)
|
||
),
|
||
...equippedItems.map((item) =>
|
||
Markup.button.callback(`🚫 Снять ${item.name}`, `unequip_item_${item.id}`)
|
||
),
|
||
];
|
||
buttons.push(Markup.button.callback('🔙 В меню', 'crime_menu'));
|
||
await ctx.editMessageText(message, Markup.inlineKeyboard(buttons, { columns: 2 }));
|
||
} catch (error) {
|
||
console.error('Ошибка при выводе инвентаря:', error);
|
||
await ctx.reply('Произошла ошибка при отображении вашего инвентаря.');
|
||
}
|
||
});
|
||
|
||
rpg.action(/view_item_(\d+)/, async (ctx) => {
|
||
const itemId = parseInt(ctx.match[1], 10);
|
||
const item = await InventoryModel.findByPk(itemId);
|
||
|
||
if (!item || item.telegram_id != ctx.from.id) {
|
||
return ctx.reply('Этот предмет не найден в вашем инвентаре.');
|
||
}
|
||
|
||
const character = await CharacterModel.findByPk(ctx.from.id);
|
||
|
||
if (!character) {
|
||
return ctx.reply('Персонаж не найден.');
|
||
}
|
||
|
||
if (item.equipped) {
|
||
return ctx.reply(`${item.name} уже снаряжен.`);
|
||
}
|
||
|
||
// Подготовка кнопок
|
||
const buttons = [];
|
||
if (!item.equipped) {
|
||
buttons.push({ text: `🎯 Использовать`, callback_data: `use_item_${item.id}` });
|
||
}
|
||
if (item.equipped && item.canBeEquipped) {
|
||
buttons.push({ text: `🚫 Снять`, callback_data: `unequip_item_${item.id}` });
|
||
}
|
||
|
||
buttons.push({ text: `💰 Продать`, callback_data: `sell_item_${item.id}` });
|
||
|
||
// Формируем сообщение с эффектами
|
||
let effectsMessage = '*Эффекты:\n*';
|
||
if (item.effectData) {
|
||
effectsMessage += getEffectDescription(item.effectData);
|
||
}
|
||
|
||
logs(ctx, "Просмотр предмета", { item });
|
||
|
||
try {
|
||
// Формируем путь к картинке
|
||
let imagePath = path.join(__dirname, 'media/items', item.text_id + '.png'); // Путь к картинке
|
||
|
||
// Проверяем существует ли файл
|
||
if (!fs.existsSync(imagePath)) {
|
||
imagePath = path.join(__dirname, 'media/items', 'template.png'); // Путь к картинке по умолчанию
|
||
}
|
||
|
||
// Отправляем фото и сообщение с кнопками
|
||
await ctx.replyWithPhoto({ source: imagePath }, { // Отправка фото из папки
|
||
caption: `🎒 _*${utils.escape(item.name)}*_\n\n*Тип:* ${utils.escape(getItemType(item.type))}\n*Описание:* \n**>${utils.escape(item.description)}\n*Цена:* ${utils.escape(utils.spaces(item.price))}Ð \\(В магазине\\)\n*Снаряжаемый:* ${utils.escape(item.canBeEquipped ? 'Да' : 'Нет')}\n${item.effectData ? effectsMessage : ""}`,
|
||
parse_mode: 'MarkdownV2', // Используем MarkdownV2 для форматирования
|
||
reply_markup: {
|
||
inline_keyboard: [buttons] // Кнопки передаем как часть структуры inline_keyboard
|
||
},
|
||
});
|
||
} catch (err) {
|
||
console.error("Ошибка при отправке сообщения:", err);
|
||
return ctx.reply('Произошла ошибка при отправке сообщения.');
|
||
}
|
||
});
|
||
|
||
rpg.action(/sell_item_(\d+)/, async (ctx) => {
|
||
const itemId = parseInt(ctx.match[1], 10);
|
||
const item = await InventoryModel.findByPk(itemId);
|
||
|
||
if (!item || item.telegram_id != ctx.from.id) {
|
||
return ctx.reply('Этот предмет не найден в вашем инвентаре.');
|
||
}
|
||
|
||
const character = await CharacterModel.findByPk(ctx.from.id);
|
||
|
||
if (!character) {
|
||
return ctx.reply('Персонаж не найден.');
|
||
}
|
||
|
||
if (item.equipped) {
|
||
return ctx.answerCbQuery(`${item.name} нельзя продать, пока он снаряжен.`);
|
||
}
|
||
|
||
// Продаем предмет (обычно за треть от цены, но в зависимости от интеллекта персонажа, цена может быть выше, вплоть до 100%)
|
||
|
||
let sellPrice = Math.round(item.price * (0.3 + character.intelligence / 100));
|
||
character.dirtymoney += sellPrice;
|
||
await InventoryModel.destroy({ where: { id: item.id } });
|
||
await character.save();
|
||
|
||
ctx.editMessageCaption(`Вы продали ${item.name} за ${utils.spaces(sellPrice)}Ð`);
|
||
|
||
logs(ctx, "Продажа предмета", { item, sellPrice });
|
||
});
|
||
|
||
rpg.action(/use_item_(\d+)/, async (ctx) => {
|
||
const itemId = parseInt(ctx.match[1], 10);
|
||
const item = await InventoryModel.findByPk(itemId);
|
||
|
||
if (!item || item.telegram_id != ctx.from.id) {
|
||
return ctx.reply('Этот предмет не найден в вашем инвентаре.');
|
||
}
|
||
|
||
const character = await CharacterModel.findByPk(ctx.from.id);
|
||
|
||
if (!character) {
|
||
return ctx.reply('Персонаж не найден.');
|
||
}
|
||
|
||
if (item.equipped) {
|
||
return ctx.reply(`${item.name} уже снаряжен.`);
|
||
}
|
||
|
||
if (!item.canBeEquipped) {
|
||
return ctx.answerCbQuery(`🚫 ${item.name} нельзя сейчас использовать.`);
|
||
}
|
||
|
||
// Проверяем, не надеты ли другие предметы того же типа
|
||
const equippedItems = await InventoryModel.findAll({
|
||
where: { telegram_id: ctx.from.id, type: item.type, equipped: true }
|
||
});
|
||
|
||
if (equippedItems.length > 0) {
|
||
return ctx.answerCbQuery(`🚫 Вы не можете использовать ${item.name}, пока надеты другие предметы того же типа.`, { show_alert: true } );
|
||
}
|
||
|
||
// Применяем эффекты предмета
|
||
if (item.effectData) {
|
||
const resultMessages = processEffects(character, item.effectData, true);
|
||
await ctx.answerCbQuery(resultMessages, { show_alert: true });
|
||
}
|
||
|
||
if (item.canBeEquipped && item.type != 'consumable') {
|
||
item.equipped = true;
|
||
await item.save();
|
||
ctx.reply(`Вы снарядили ${item.name}.`);
|
||
}
|
||
if (item.canBeEquipped && item.type == 'consumable') {
|
||
await InventoryModel.destroy({ where: { id: item.id } });
|
||
ctx.reply(`Вы использовали ${item.name}.`);
|
||
}
|
||
});
|
||
|
||
rpg.action(/unequip_item_(\d+)/, async (ctx) => {
|
||
const itemId = parseInt(ctx.match[1], 10);
|
||
const character = await CharacterModel.findByPk(ctx.from.id);
|
||
const item = await InventoryModel.findByPk(itemId);
|
||
|
||
if (!item || item.telegram_id != ctx.from.id) {
|
||
return ctx.answerCbQuery('Этот предмет не найден в вашем инвентаре.');
|
||
}
|
||
|
||
if (!item.equipped) {
|
||
return ctx.answerCbQuery(`${item.name} не снаряжен.`);
|
||
}
|
||
|
||
if (item.effectData) {
|
||
const resultMessages = processEffects(character, item.effectData, false);
|
||
await ctx.editMessageText(resultMessages);
|
||
}
|
||
|
||
item.equipped = false;
|
||
await item.save();
|
||
|
||
ctx.answerCbQuery(`Вы успешно сняли ${item.name}.`);
|
||
});
|
||
|
||
rpg.action('shop', async (ctx) => {
|
||
// Выводим красиво кнопки разделов магазина "Оружие", "Броня", "Аксессуары" и т.д.
|
||
await ctx.editMessageText('🛒 Магазин', Markup.inlineKeyboard([
|
||
[{ text: 'Оружие', callback_data: 'shop_weapon' }],
|
||
[{ text: 'Броня', callback_data: 'shop_armor' }],
|
||
[{ text: 'Аксессуары', callback_data: 'shop_accessory' }],
|
||
[{ text: 'Расходники', callback_data: 'shop_consumable' }],
|
||
[{ text: 'Импланты', callback_data: 'shop_implant' }],
|
||
[{ text: '🔙 В меню', callback_data: 'crime_menu' }],
|
||
]));
|
||
});
|
||
|
||
rpg.action('shop_weapon', async (ctx) => {
|
||
const items = await ItemsModel.findAll({ where: { type: 'weapon' } });
|
||
const world = await WorldModel.findOne({ where: { id: 1 } });
|
||
|
||
if (items.length === 0) {
|
||
return ctx.editMessageText('В магазине нет оружия.');
|
||
}
|
||
|
||
// Проверяем есть ли найденные предметы (id) в world.itemsInCrimeShop
|
||
let itemsInCrimeShop = items.filter((item) => world.itemsInCrimeShop.includes(item.id));
|
||
|
||
if (itemsInCrimeShop.length === 0) {
|
||
return ctx.editMessageText('В магазине нет оружия.');
|
||
}
|
||
|
||
let message = '*🛒 Оружие в магазине:*\n\n'
|
||
itemsInCrimeShop.forEach((item) => {
|
||
message += `🔹 ${item.name} - ${item.price}₽\n`;
|
||
}
|
||
);
|
||
|
||
await ctx.editMessageText(message, Markup.inlineKeyboard([
|
||
itemsInCrimeShop.map((item) => Markup.button.callback(`Купить ${item.name}`, `buy_item_${item.id}`)),
|
||
[{ text: '🔙 В меню', callback_data: 'shop' }]
|
||
]));
|
||
});
|
||
|
||
rpg.action('shop_armor', async (ctx) => {
|
||
const items = await ItemsModel.findAll({ where: { type: 'armor' } });
|
||
const world = await WorldModel.findOne({ where: { id: 1 } });
|
||
|
||
if (items.length === 0) {
|
||
return ctx.editMessageText('В магазине нет брони.');
|
||
}
|
||
|
||
// Проверяем есть ли найденные предметы (id) в world.itemsInCrimeShop
|
||
let itemsInCrimeShop = items.filter((item) => world.itemsInCrimeShop.includes(item.id));
|
||
|
||
if (itemsInCrimeShop.length === 0) {
|
||
return ctx.editMessageText('В магазине нет брони.');
|
||
}
|
||
|
||
let message = '*🛒 Броня в магазине:*\n\n'
|
||
itemsInCrimeShop.forEach((item) => {
|
||
message += `🔹 ${item.name} - ${item.price}₽\n`;
|
||
}
|
||
);
|
||
|
||
await ctx.editMessageText(message, Markup.inlineKeyboard([
|
||
itemsInCrimeShop.map((item) => Markup.button.callback(`Купить ${item.name}`, `buy_item_${item.id}`)),
|
||
[{ text: '🔙 В меню', callback_data: 'shop' }]
|
||
]));
|
||
});
|
||
|
||
rpg.action('shop_accessory', async (ctx) => {
|
||
const items = await ItemsModel.findAll({ where: { type: 'accessory' } });
|
||
const world = await WorldModel.findOne({ where: { id: 1 } });
|
||
|
||
if (items.length === 0) {
|
||
return ctx.editMessageText('В магазине нет аксессуаров.');
|
||
}
|
||
|
||
// Проверяем есть ли найденные предметы (id) в world.itemsInCrimeShop
|
||
let itemsInCrimeShop = items.filter((item) => world.itemsInCrimeShop.includes(item.id));
|
||
|
||
if (itemsInCrimeShop.length === 0) {
|
||
return ctx.editMessageText('В магазине нет аксессуаров.');
|
||
}
|
||
|
||
let message = '*🛒 Аксессуары в магазине:*\n\n'
|
||
itemsInCrimeShop.forEach((item) => {
|
||
message += `🔹 ${item.name} - ${item.price}₽\n`;
|
||
}
|
||
);
|
||
|
||
await ctx.editMessageText(message, Markup.inlineKeyboard([
|
||
itemsInCrimeShop.map((item) => Markup.button.callback(`Купить ${item.name}`, `buy_item_${item.id}`)),
|
||
[{ text: '🔙 В меню', callback_data: 'shop' }]
|
||
]));
|
||
});
|
||
|
||
rpg.action('shop_consumable', async (ctx) => {
|
||
const items = await ItemsModel.findAll({ where: { type: 'consumable' } });
|
||
const world = await WorldModel.findOne({ where: { id: 1 } });
|
||
|
||
if (items.length === 0) {
|
||
return ctx.editMessageText('В магазине нет расходников.');
|
||
}
|
||
|
||
// Проверяем есть ли найденные предметы (id) в world.itemsInCrimeShop
|
||
let itemsInCrimeShop = items.filter((item) => world.itemsInCrimeShop.includes(item.id));
|
||
|
||
if (itemsInCrimeShop.length === 0) {
|
||
return ctx.editMessageText('В магазине нет расходников.');
|
||
}
|
||
|
||
let message = '*🛒 Расходники в магазине:*\n\n'
|
||
itemsInCrimeShop.forEach((item) => {
|
||
message += `🔹 ${item.name} - ${item.price}₽\n`;
|
||
}
|
||
);
|
||
|
||
await ctx.editMessageText(message, Markup.inlineKeyboard([
|
||
itemsInCrimeShop.map((item) => Markup.button.callback(`Купить ${item.name}`, `buy_item_${item.id}`)),
|
||
[{ text: '🔙 В меню', callback_data: 'shop' }]
|
||
]))
|
||
});
|
||
|
||
rpg.action('shop_implant', async (ctx) => {
|
||
const items = await ItemsModel.findAll({ where: { type: 'implant' } });
|
||
const world = await WorldModel.findOne({ where: { id: 1 } });
|
||
|
||
if (items.length === 0) {
|
||
return ctx.editMessageText('В магазине нет имплантов.');
|
||
}
|
||
|
||
// Проверяем есть ли найденные предметы (id) в world.itemsInCrimeShop
|
||
let itemsInCrimeShop = items.filter((item) => world.itemsInCrimeShop.includes(item.id));
|
||
|
||
if (itemsInCrimeShop.length === 0) {
|
||
return ctx.editMessageText('В магазине нет имплантов.');
|
||
}
|
||
|
||
let message = '*🛒 Импланты в магазине:*\n\n'
|
||
itemsInCrimeShop.forEach((item) => {
|
||
message += `🔹 ${item.name} - ${item.price}₽\n`;
|
||
}
|
||
);
|
||
|
||
await ctx.editMessageText(message, Markup.inlineKeyboard([
|
||
itemsInCrimeShop.map((item) => Markup.button.callback(`Купить ${item.name}`, `buy_item_${item.id}`)),
|
||
[{ text: '🔙 В меню', callback_data: 'shop' }]
|
||
]));
|
||
});
|
||
|
||
|
||
rpg.action(/buy_item_(\d+)/, async (ctx) => {
|
||
const itemId = parseInt(ctx.match[1], 10);
|
||
const item = await ItemsModel.findByPk(itemId);
|
||
const inventory = await InventoryModel.findAll({ where: { telegram_id: ctx.from.id } });
|
||
|
||
if (inventory.length >= 15) {
|
||
return ctx.answerCbQuery('Инвентарь полон.', { show_alert: true });
|
||
}
|
||
|
||
if (!item) {
|
||
return ctx.reply('Предмет не найден.');
|
||
}
|
||
|
||
const character = await CharacterModel.findByPk(ctx.from.id);
|
||
|
||
if (!character) {
|
||
return ctx.reply('Персонаж не найден.');
|
||
}
|
||
|
||
if (character.balance < item.price) {
|
||
return ctx.answerCbQuery('У вас недостаточно средств для покупки этого предмета.', { show_alert: true });
|
||
}
|
||
|
||
// Снимаем деньги с баланса
|
||
character.dirtymoney -= item.price;
|
||
|
||
// Добавляем предмет в инвентарь
|
||
await InventoryModel.create({
|
||
telegram_id: ctx.from.id,
|
||
name: item.name,
|
||
text_id: item.text_id,
|
||
description: item.description,
|
||
effectData: item.effectData,
|
||
price: item.price,
|
||
rarity: item.rarity,
|
||
type: item.type,
|
||
duration: item.duration,
|
||
canBeEquipped: item.canBeEquipped,
|
||
equipped: false,
|
||
img: item.img
|
||
});
|
||
|
||
await character.save();
|
||
ctx.answerCbQuery(`Вы успешно купили ${item.name} за ${item.price}₽!`, { show_alert: true });
|
||
});
|
||
|
||
//Команда для админа, выдача предмета по id игроку
|
||
rpg.command('giveitem', async (ctx) => {
|
||
if (ctx.from.id != 275416286) return ctx.reply('Команда недоступна.')
|
||
const itemId = parseInt(ctx.message.text.split(' ')[1], 10);
|
||
let user = await UserModel.findByPk(ctx.from.id)
|
||
let character = await CharacterModel.findByPk(ctx.from.id);
|
||
let item = await ItemsModel.findByPk(itemId);
|
||
let inventory = await InventoryModel.findAll({ where: { telegram_id: ctx.from.id } });
|
||
if (inventory.length >= 15) {
|
||
return ctx.reply('Инвентарь полон.');
|
||
}
|
||
if (!item) {
|
||
return ctx.reply('Предмет не найден.');
|
||
}
|
||
if (!character) {
|
||
return ctx.reply('Персонаж не найден.');
|
||
}
|
||
await InventoryModel.create({
|
||
telegram_id: ctx.from.id,
|
||
name: item.name,
|
||
text_id: item.text_id,
|
||
description: item.description,
|
||
effectData: item.effectData,
|
||
price: item.price,
|
||
rarity: item.rarity,
|
||
type: item.type,
|
||
duration: item.duration,
|
||
canBeEquipped: item.canBeEquipped,
|
||
equipped: false,
|
||
img: item.img
|
||
});
|
||
await character.save();
|
||
ctx.reply(`Вы успешно купили ${item.name} за ${item.price}₽!`);
|
||
}
|
||
);
|
||
|
||
////////////////////////////////////////////////////////////////////////////////////////////
|
||
|
||
CharacterModel.prototype.addEquippedItem = async function (item) {
|
||
// Проверяем, находится ли предмет в инвентаре персонажа
|
||
const inventoryItem = await InventoryModel.findOne({
|
||
where: { telegram_id: this.telegram_id, id: item.id },
|
||
});
|
||
|
||
if (!inventoryItem) {
|
||
throw new Error('Предмет не найден в вашем инвентаре.');
|
||
}
|
||
|
||
// Проверяем, не снаряжен ли уже этот предмет
|
||
if (inventoryItem.equipped) {
|
||
throw new Error('Этот предмет уже снаряжен.');
|
||
}
|
||
|
||
// Снаряжаем предмет
|
||
inventoryItem.equipped = true;
|
||
await inventoryItem.save();
|
||
|
||
// Применяем эффекты предмета
|
||
let resultMessages = [];
|
||
if (inventoryItem.effectData) {
|
||
resultMessages = processEffects(this, inventoryItem.effectData, true);
|
||
await this.save(); // Сохраняем изменения в характеристиках персонажа
|
||
}
|
||
|
||
return resultMessages;
|
||
};
|
||
|
||
CharacterModel.prototype.removeEquippedItem = async function (item) {
|
||
// Проверяем, находится ли предмет в инвентаре персонажа
|
||
const inventoryItem = await InventoryModel.findOne({
|
||
where: { telegram_id: this.telegram_id, id: item.id },
|
||
});
|
||
|
||
if (!inventoryItem || !inventoryItem.equipped) {
|
||
throw new Error('Этот предмет не снаряжен.');
|
||
}
|
||
|
||
// Снимаем предмет
|
||
inventoryItem.equipped = false;
|
||
await inventoryItem.save();
|
||
|
||
// Убираем эффекты предмета
|
||
let resultMessages = [];
|
||
if (inventoryItem.effectData) {
|
||
resultMessages = processEffects(this, inventoryItem.effectData, false);
|
||
await this.save(); // Сохраняем изменения в характеристиках персонажа
|
||
}
|
||
|
||
return resultMessages;
|
||
};
|
||
|
||
const processEffects = (character, effects, isEquipping) => {
|
||
let messages = [];
|
||
|
||
effects.forEach((effect) => {
|
||
if (!isEquipping) {
|
||
// Снимаем эффекты
|
||
switch (effect.type) {
|
||
case 'damage_boost':
|
||
character.force = Math.max(0, character.force - effect.amount);
|
||
character.save()
|
||
messages.push(`Ваш урон уменьшен на ${effect.amount}.`);
|
||
break;
|
||
|
||
case 'max_health_boost':
|
||
character.max_hp = Math.max(1, character.max_hp - effect.amount);
|
||
if (character.hp > character.max_hp) {
|
||
character.hp = character.max_hp;
|
||
}
|
||
character.save()
|
||
messages.push(`Ваше максимальное здоровье уменьшено на ${effect.amount}.`);
|
||
break;
|
||
|
||
case 'stamina_penalty':
|
||
character.max_stamina += effect.amount;
|
||
character.save()
|
||
messages.push(`Ваша стамина увеличена на ${effect.amount}.`);
|
||
break;
|
||
|
||
case 'intelligence_boost':
|
||
character.intelligence = Math.max(0, character.intelligence - effect.amount);
|
||
character.save()
|
||
messages.push(`Ваш интеллект уменьшена на ${effect.amount}.`);
|
||
break;
|
||
|
||
case 'resilience_boost':
|
||
character.resilience = Math.max(0, character.resilience - effect.amount);
|
||
character.save()
|
||
messages.push(`Ваша устойчивость уменьшена на ${effect.amount}.`);
|
||
break;
|
||
|
||
case 'endurance_boost':
|
||
character.endurance = Math.max(0, character.endurance - effect.amount);
|
||
character.save()
|
||
messages.push(`Ваша выносливость уменьшена на ${effect.amount}.`);
|
||
break;
|
||
|
||
case 'force_penalty':
|
||
character.force += effect.amount;
|
||
character.save()
|
||
messages.push(`Ваш урон увеличен на ${effect.amount}.`);
|
||
break;
|
||
|
||
case 'intelligence_penalty':
|
||
character.intelligence += effect.amount;
|
||
character.save()
|
||
messages.push(`Ваш интеллект увеличен на ${effect.amount}.`);
|
||
break;
|
||
|
||
case 'resilience_penalty':
|
||
character.resilience += effect.amount;
|
||
character.save()
|
||
messages.push(`Ваша устойчивость увеличена на ${effect.amount}.`);
|
||
break;
|
||
|
||
case 'endurance_penalty':
|
||
character.endurance += effect.amount;
|
||
character.save()
|
||
messages.push(`Ваша выносливость увеличена на ${effect.amount}.`);
|
||
break;
|
||
|
||
default:
|
||
messages.push('Неизвестный эффект при снятии.');
|
||
}
|
||
} else {
|
||
// Применяем эффекты
|
||
switch (effect.type) {
|
||
case 'heal':
|
||
character.hp = Math.min(character.max_hp, character.hp + effect.amount);
|
||
character.save()
|
||
messages.push(`Вы восстановили ${effect.amount} HP.`);
|
||
break;
|
||
|
||
case 'damage_boost':
|
||
character.force += effect.amount;
|
||
character.save()
|
||
messages.push(`Ваша сила увеличена на ${effect.amount}.`);
|
||
break;
|
||
|
||
case 'max_health_boost':
|
||
character.max_hp += effect.amount;
|
||
character.hp += effect.amount;
|
||
character.save()
|
||
messages.push(`Ваше максимальное здоровье увеличено на ${effect.amount}.`);
|
||
break;
|
||
|
||
case 'stamina_penalty':
|
||
character.max_stamina -= effect.amount;
|
||
character.save()
|
||
messages.push(`Ваша стамина уменьшена на ${effect.amount}.`);
|
||
break;
|
||
|
||
case 'stamina_recover':
|
||
character.stamina = Math.min(character.max_stamina, character.stamina + effect.amount);
|
||
character.save()
|
||
messages.push(`Вы восстановили ${effect.amount} стамины.`);
|
||
break;
|
||
|
||
case 'intelligence_boost':
|
||
character.intelligence += effect.amount;
|
||
character.save()
|
||
messages.push(`Ваш интеллект увеличен на ${effect.amount}.`);
|
||
break;
|
||
|
||
case 'resilience_boost':
|
||
character.resilience += effect.amount;
|
||
character.save()
|
||
messages.push(`Ваша устойчивость увеличена на ${effect.amount}.`);
|
||
break;
|
||
|
||
case 'endurance_boost':
|
||
character.endurance += effect.amount;
|
||
character.save()
|
||
messages.push(`Ваша выносливость увеличена на ${effect.amount}.`);
|
||
break;
|
||
|
||
case 'force_penalty':
|
||
character.force -= effect.amount;
|
||
character.save()
|
||
messages.push(`Ваш урон уменьшен на ${effect.amount}.`);
|
||
break
|
||
|
||
case 'intelligence_penalty':
|
||
character.intelligence -= effect.amount;
|
||
character.save()
|
||
messages.push(`Ваш интеллект уменьшен на ${effect.amount}.`);
|
||
break
|
||
|
||
case 'resilience_penalty':
|
||
character.resilience -= effect.amount;
|
||
character.save()
|
||
messages.push(`Ваша устойчивость уменьшена на ${effect.amount}.`);
|
||
break
|
||
|
||
case 'endurance_penalty':
|
||
character.endurance -= effect.amount;
|
||
character.save()
|
||
messages.push(`Ваша выносливость уменьшена на ${effect.amount}.`);
|
||
break
|
||
|
||
default:
|
||
messages.push('Неизвестный эффект при снаряжении.');
|
||
}
|
||
}
|
||
});
|
||
|
||
return messages.join('\n');
|
||
};
|
||
|
||
// Функция для читаемого отображения эффектов предметов (пример item.effectData: "[{""type"": ""max_health_boost"", ""amount"": 100}, {""type"": ""resilience_boost"", ""amount"": 20}, {""type"": ""intelligence_boost"", ""amount"": 75}, {""type"": ""endurance_boost"", ""amount"": 15}]")
|
||
function getEffectDescription(effectData) {
|
||
const effects = effectData;
|
||
let description = '';
|
||
|
||
effects.forEach((effect) => {
|
||
switch (effect.type) {
|
||
case 'heal':
|
||
description += `Восстанавливает ${effect.amount} HP\n`;
|
||
break;
|
||
|
||
case 'damage_boost':
|
||
description += `Увеличивает урон на ${effect.amount}\n`;
|
||
break;
|
||
|
||
case 'max_health_boost':
|
||
description += `Увеличивает максимальное здоровье на ${effect.amount}\n`;
|
||
break;
|
||
|
||
case 'stamina_penalty':
|
||
description += `Уменьшает стамина на ${effect.amount}\n`;
|
||
break;
|
||
|
||
case 'stamina_recover':
|
||
description += `Восстанавливает ${effect.amount} стамины\n`;
|
||
break;
|
||
|
||
case 'intelligence_boost':
|
||
description += `Увеличивает интеллект на ${effect.amount}\n`;
|
||
break;
|
||
|
||
case 'resilience_boost':
|
||
description += `Увеличивает устойчивость на ${effect.amount}\n`;
|
||
break;
|
||
|
||
case 'endurance_boost':
|
||
description += `Увеличивает выносливость на ${effect.amount}\n`;
|
||
break;
|
||
|
||
case 'force_penalty':
|
||
description += `Уменьшает урон на ${effect.amount}\n`;
|
||
break;
|
||
|
||
case 'intelligence_penalty':
|
||
description += `Уменьшает интеллект на ${effect.amount}\n`;
|
||
break;
|
||
|
||
case 'resilience_penalty':
|
||
description += `Уменьшает устойчивость на ${effect.amount}\n`;
|
||
break;
|
||
|
||
case 'endurance_penalty':
|
||
description += `Уменьшает выносливость на ${effect.amount}\n`;
|
||
break;
|
||
|
||
default:
|
||
description += 'Неизвестный эффект\n';
|
||
}
|
||
});
|
||
|
||
return description;
|
||
}
|
||
|
||
|
||
function generateKeyboard() {
|
||
const buttonsCount = 10;
|
||
const buttons = [];
|
||
const winButton = utils.rand(1, 10);
|
||
|
||
for (let i = 1; i <= buttonsCount; i++) {
|
||
if (i === winButton) {
|
||
buttons.push({ text: `🔒`, callback_data: `SHOP_CASH_BREAK_SUCCESS` });
|
||
} else {
|
||
buttons.push({ text: `🔒`, callback_data: `lock_${i}` });
|
||
}
|
||
}
|
||
|
||
const rows = [];
|
||
while (buttons.length > 0) {
|
||
rows.push(buttons.splice(0, 5)); // Разбиваем на подмассивы по 5 кнопок
|
||
}
|
||
console.log(rows)
|
||
return {
|
||
reply_markup: {
|
||
inline_keyboard: rows,
|
||
},
|
||
};
|
||
}
|
||
|
||
function removeButton(ctx, buttonId) {
|
||
const keyboard = ctx.update.callback_query.message.reply_markup.inline_keyboard;
|
||
|
||
for (let row of keyboard) {
|
||
for (let i = 0; i < row.length; i++) {
|
||
if (row[i].callback_data === buttonId) {
|
||
row[i].text = `🔓`;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
ctx.editMessageText('Взлом замка:', Markup.inlineKeyboard(keyboard).resize());
|
||
}
|
||
|
||
// Восстановление здоровья (HP)
|
||
async function recoverHealth() {
|
||
const characters = await CharacterModel.findAll()
|
||
// Восстанавливаем только тем, у кого HP меньше максимума
|
||
for (const character of characters) {
|
||
if (character.hp >= character.max_hp) {
|
||
continue; // Пропускаем, если здоровье полное
|
||
}
|
||
let recoveryRate = character.resilience; // Восстановление за интервал
|
||
if (recoveryRate <= 0) {
|
||
recoveryRate = 1; // Минимум 1 HP
|
||
}
|
||
character.hp = Math.min(character.hp + recoveryRate, character.max_hp); // Не превышаем максимум
|
||
await character.save();
|
||
}
|
||
}
|
||
|
||
// Восстановление выносливости (стамины)
|
||
async function recoverStamina() {
|
||
const characters = await CharacterModel.findAll()
|
||
// Восстанавливаем только тем, у кого стамина меньше максимума
|
||
for (const character of characters) {
|
||
if (character.stamina >= character.max_stamina) {
|
||
continue; // Пропускаем, если стамина полная
|
||
}
|
||
let recoveryRate = character.endurance; // Восстановление 5 стамины за интервал
|
||
if (recoveryRate <= 0) {
|
||
recoveryRate = 1; // Минимум 1 стамина
|
||
}
|
||
character.stamina = Math.min(character.stamina + recoveryRate, character.max_stamina); // Не превышаем максимум
|
||
await character.save();
|
||
}
|
||
}
|
||
|
||
const reduceStealedCards = async () => {
|
||
try {
|
||
const characters = await CharacterModel.findAll();
|
||
|
||
for (const character of characters) {
|
||
const stolenCards = await StolenCardsModel.findAll({
|
||
where: { userId: character.telegram_id }
|
||
});
|
||
|
||
if (stolenCards.length > 0) {
|
||
// Для каждой карточки уменьшаем баланс на половину
|
||
for (const card of stolenCards) {
|
||
card.balance = Math.ceil(card.balance / 2); // Баланс делим на 2 и округляем вверх
|
||
await card.save(); // Сохраняем изменения карточки
|
||
}
|
||
|
||
// Удаляем половину карточек
|
||
const cardsToDelete = Math.ceil(stolenCards.length / 2);
|
||
const cardsToRemove = stolenCards.slice(0, cardsToDelete);
|
||
|
||
for (const card of cardsToRemove) {
|
||
await card.destroy(); // Удаляем карточки
|
||
}
|
||
|
||
console.log(`Карточки для персонажа ${character.name} успешно обновлены.`);
|
||
}
|
||
}
|
||
|
||
console.log('Карточки всех игроков успешно обновлены.');
|
||
} catch (error) {
|
||
console.error('Ошибка при обновлении карточек:', error);
|
||
}
|
||
};
|
||
|
||
|
||
async function generateCard(userId, balance) {
|
||
if (!Number(balance)) {
|
||
balance = faker.finance.amount({ dec: 0 }) * 100
|
||
console.log('Random balance: ' + balance)
|
||
} else {
|
||
balance = Math.floor(balance);
|
||
console.log('Set balance: ' + balance)
|
||
}
|
||
const cardNumber = `${faker.finance.creditCardNumber()}`; // Генерация номера карты
|
||
const pin = `${faker.finance.pin()}`; // Четырехзначный ПИН
|
||
const cvv = `${faker.finance.creditCardCVV()}`; // Трехзначный CVV
|
||
const holderName = `${faker.person.firstName()} ${faker.person.lastName()}`; // Имя и фамилия
|
||
const bankName = faker.company.name(); // Название банка
|
||
const expiresDate = `${Math.floor(Math.random() * 12) + 1}/${2025 + Math.floor(Math.random() * 5)}`; // Срок действия
|
||
|
||
await StolenCardsModel.create({
|
||
userId,
|
||
balance,
|
||
cardNumber,
|
||
pin,
|
||
cvv,
|
||
holderName,
|
||
bankName,
|
||
expiresDate,
|
||
});
|
||
}
|
||
|
||
async function Exp(ctx, character, experience) {
|
||
character.exp += experience
|
||
await ctx.sendMessage(`❇️ +${experience} exp.`)
|
||
await character.save()
|
||
for (i in expToUp) {
|
||
if (character.exp >= expToUp[character.level]) {
|
||
character.exp -= expToUp[character.level]
|
||
character.level += 1
|
||
character.firePoints += 1
|
||
await character.save()
|
||
await ctx.sendMessage(`⤴️ Уровень вашего персонажа повысился до ${character.level}!`)
|
||
}
|
||
}
|
||
}
|
||
|
||
// Периодическое выполнение задач
|
||
function startRecoveryIntervals() {
|
||
const interval = 60000; // 1 мин Интервал в миллисекундах (например, 1 минута)
|
||
setInterval(recoverHealth, interval);
|
||
setInterval(recoverStamina, interval);
|
||
}
|
||
|
||
// Планируем выполнение задачи каждый день в 12 ночи
|
||
schedule.scheduleJob('0 0 * * *', reduceStealedCards);
|
||
|
||
rpg.command('force_reduce', async (ctx) => {
|
||
reduceStealedCards()
|
||
return await ctx.reply(`Принудительно`)
|
||
})
|
||
|
||
rpg.command('steal_card', async (ctx) => {
|
||
await generateCard(ctx.from.id)
|
||
ctx.reply('💳 Вы украли новую карточку! Проверьте её в вашем инвентаре.');
|
||
});
|
||
|
||
|
||
startRecoveryIntervals()
|
||
|
||
|
||
|
||
|
||
const fillEnemies = async () => {
|
||
const enemies = [
|
||
{ name: "Карманник", description: "Мелкий вор, пытающийся выудить деньги у прохожих", level: 1, hp: 40, damage: 5, loot: [1], rarity: 1, experience: 10 },
|
||
{ name: "Гопник", description: "Уличный хулиган с цепью", level: 2, hp: 70, damage: 15, loot: [2], rarity: 2, experience: 15 },
|
||
{ name: "Пьяный бродяга", description: "Пьяный человек, не опасен, но может ударить при попытке забрать его бутылку", level: 1, hp: 30, damage: 3, loot: [3], rarity: 1, experience: 5 },
|
||
{ name: "Угонщик", description: "Опытный угонщик машин, вооружённый пистолетом", level: 5, hp: 100, damage: 20, loot: [4], rarity: 2, experience: 25 },
|
||
];
|
||
|
||
for (const enemy of enemies) {
|
||
await Enemy.create(enemy);
|
||
}
|
||
console.log("Enemies filled.");
|
||
};
|
||
|
||
|
||
const generateBattles = async () => {
|
||
const locations = await Location.findAll();
|
||
|
||
for (const location of locations) {
|
||
// Проверяем количество битв для текущей локации
|
||
const existingBattlesCount = await Battle.count({ where: { location: location.id, status: 'inactive' } });
|
||
|
||
if (existingBattlesCount >= 10) {
|
||
console.log(`Пропущена локация ${location.id}, уже есть 10 битв.`);
|
||
continue; // Пропускаем локацию, если уже есть 10 битв
|
||
}
|
||
|
||
const enemies = location.enemies;
|
||
const generatedEnemies = enemies.map((enemyId) => {
|
||
const rarity = Math.random();
|
||
return rarity < 0.7 ? enemyId : null; // Шанс 70% для обычных врагов
|
||
}).filter(Boolean);
|
||
|
||
for (const enemyId of generatedEnemies) {
|
||
const enemy = await Enemy.findByPk(enemyId);
|
||
if (!enemy) continue;
|
||
|
||
// Проверяем, нужно ли создавать битвы (не превышать лимит в 10)
|
||
const currentBattlesCount = await Battle.count({ where: { location: location.id, status: 'inactive' } });
|
||
if (currentBattlesCount >= 5) {
|
||
console.log(`Достигнут лимит битв для локации ${location.id}.`);
|
||
break;
|
||
}
|
||
|
||
await Battle.create({
|
||
enemy: enemy.id,
|
||
location: location.id,
|
||
character: null,
|
||
enemy_hp: enemy.hp,
|
||
status: "inactive",
|
||
logs: [],
|
||
});
|
||
}
|
||
}
|
||
console.log("Battles generated.");
|
||
};
|
||
|
||
|
||
generateBattles()
|
||
schedule.scheduleJob('0 * * * *', generateBattles); // Каждый час в начале часа
|
||
|
||
|
||
|
||
rpg.action("locations", async (ctx) => {
|
||
const locations = await Location.findAll();
|
||
const character = await CharacterModel.findOne({ where: { telegram_id: ctx.from.id } });
|
||
if (!locations.length) {
|
||
return ctx.reply("Нет доступных локаций для исследования.");
|
||
}
|
||
// Создаем кнопки для каждой локации и сортируем их по уровню
|
||
const buttons = locations
|
||
.sort((a, b) => a.level - b.level)
|
||
.map((location) => {
|
||
return Markup.button.callback(location.name + ` [${character.level < location.level + 3 ? `⚠️ ` + `${location.level} - ${location.level + 3}` : `${location.level} - ${location.level + 3}`} Ур.]`, `viewlocation_${location.id}`);
|
||
});
|
||
const keyboard = Markup.inlineKeyboard(buttons, { columns: 1 });
|
||
await ctx.reply("Выберите локацию для исследования:", keyboard);
|
||
});
|
||
|
||
rpg.action(/viewlocation_\d+/, async (ctx) => {
|
||
const locationId = ctx.match[0].split("_")[1];
|
||
const location = await Location.findByPk(locationId);
|
||
|
||
if (!location) {
|
||
return ctx.reply("Локация не найдена.");
|
||
}
|
||
|
||
const lootItems = location.loot.length
|
||
? location.loot.map((id) => `#${id}`).join(", ")
|
||
: "Нет";
|
||
|
||
const description = `🏞️ ${location.name}\n\n${location.description}\n\nУровень: ${location.level}\nДобыча: ${lootItems}\n`;
|
||
const keyboard = Markup.inlineKeyboard([
|
||
[{ text: "Исследовать", callback_data: `explore_${location.id}` }],
|
||
[{ text: "Охота", callback_data: `hunt_location_${location.id}` }],
|
||
]);
|
||
|
||
await ctx.editMessageText(description, keyboard);
|
||
});
|
||
|
||
rpg.action(/explore_\d+/, async (ctx) => {
|
||
return ctx.answerCbQuery("Функционал в разработке.");
|
||
const locationId = ctx.match[0].split("_")[1];
|
||
const location = await Location.findByPk(locationId);
|
||
|
||
if (!location) {
|
||
return ctx.reply("Локация не найдена.");
|
||
}
|
||
|
||
const character = await CharacterModel.findOne({ where: { telegram_id: ctx.from.id } });
|
||
|
||
if (!character) {
|
||
return ctx.reply("Персонаж не найден.");
|
||
}
|
||
|
||
// Проверяем, не превышает ли уровень локации уровень персонажа на 3
|
||
if (character.level < location.level || character.level > location.level + 3) {
|
||
return ctx.reply("📛 Здесь слишком опасно.");
|
||
}
|
||
|
||
// Проверяем стамину
|
||
if (character.stamina < location.level) {
|
||
return ctx.reply("📛 У вас недостаточно стамины для исследования.");
|
||
}
|
||
|
||
character.stamina -= location.level; // Снимаем стамину за исследование
|
||
await character.save();
|
||
// У локации такой же свой массив лута как и у врагов, поэтому можно использовать ту же функцию dropLoot
|
||
const loot = dropItem(location.loot);
|
||
const experience = Math.floor(Math.random() * 10) + 5; // Опыт за исследование
|
||
await Exp(ctx, character, experience)
|
||
await ctx.reply(`Вы исследовали локацию ${location.name} и нашли:\n${loot.join("\n")}\n\n`);
|
||
});
|
||
|
||
rpg.action(/hunt_location_\d+/, async (ctx) => {
|
||
const locationId = ctx.match[0].split('_')[2]; // Получаем ID локации
|
||
const location = await Location.findByPk(locationId);
|
||
|
||
if (!location) {
|
||
logs(ctx, "Локация не найдена", { locationId });
|
||
return ctx.reply("Локация не найдена.");
|
||
}
|
||
|
||
const character = await CharacterModel.findOne({ where: { telegram_id: ctx.from.id } });
|
||
if (!character) {
|
||
logs(ctx, "Персонаж не найден", { telegramId: ctx.from.id });
|
||
return ctx.reply("Ваш персонаж не найден.");
|
||
}
|
||
|
||
const inventory = await InventoryModel.findAll({ where: { telegram_id: ctx.from.id } });
|
||
const hasGrenadeLow = inventory.some((item) => item.text_id === "grenade_low");
|
||
const hasGrenadeMedium = inventory.some((item) => item.text_id === "grenade_medium");
|
||
const hasGrenadeHard = inventory.some((item) => item.text_id === "grenade_hard");
|
||
|
||
const enemyIds = location.enemies || [];
|
||
if (enemyIds.length === 0) {
|
||
logs(ctx, "Нет врагов в локации", { location });
|
||
return ctx.answerCbQuery("В этой локации сейчас нет врагов. Попробуйте позже.");
|
||
}
|
||
|
||
const activeBattles = await Battle.findAll({
|
||
where: { location: location.id, status: "inactive" },
|
||
});
|
||
|
||
if (activeBattles.length === 0) {
|
||
logs(ctx, "Нет активных врагов в локации", { location });
|
||
return ctx.answerCbQuery("В этой локации сейчас нет активных врагов. Попробуйте позже.");
|
||
}
|
||
|
||
const activeEnemyIds = activeBattles.map((battle) => battle.enemy);
|
||
const enemies = await Enemy.findAll({ where: { id: activeEnemyIds } });
|
||
|
||
if (enemies.length === 0) {
|
||
logs(ctx, "Нет врагов в локации", { location });
|
||
return ctx.answerCbQuery("В этой локации сейчас нет врагов.");
|
||
}
|
||
|
||
const buttons = activeBattles.map((battle) => {
|
||
const enemy = enemies.find((e) => e.id === battle.enemy);
|
||
return {
|
||
text: `${enemy.name} (HP: ${battle.enemy_hp})`,
|
||
callback_data: `start_battle_${battle.id}`,
|
||
};
|
||
});
|
||
|
||
if (hasGrenadeLow || hasGrenadeMedium || hasGrenadeHard) {
|
||
buttons.push({
|
||
text: "💣 Использовать гранату",
|
||
callback_data: `use_grenade_${locationId}`,
|
||
});
|
||
}
|
||
|
||
const keyboard = Markup.inlineKeyboard(buttons.map((btn) => [btn]));
|
||
|
||
logs(ctx, "Выбор врага для охоты", { location, enemies });
|
||
await ctx.editMessageText(
|
||
`🔍 Локация: ${location.name}\n\nВыберите врага для охоты:`,
|
||
keyboard
|
||
);
|
||
});
|
||
|
||
// Использование гранаты
|
||
rpg.action(/use_grenade_\d+/, async (ctx) => {
|
||
const locationId = ctx.match[0].split('_')[2];
|
||
const location = await Location.findByPk(locationId);
|
||
|
||
if (!location) {
|
||
logs(ctx, "Локация не найдена", { locationId });
|
||
return ctx.reply("Локация не найдена.");
|
||
}
|
||
|
||
const character = await CharacterModel.findOne({ where: { telegram_id: ctx.from.id } });
|
||
if (!character) {
|
||
logs(ctx, "Персонаж не найден", { telegramId: ctx.from.id });
|
||
return ctx.reply("Ваш персонаж не найден.");
|
||
}
|
||
|
||
const inventory = await InventoryModel.findAll({ where: { telegram_id: ctx.from.id } });
|
||
const grenade =
|
||
inventory.find((item) => item.text_id === "grenade_hard") ||
|
||
inventory.find((item) => item.text_id === "grenade_medium") ||
|
||
inventory.find((item) => item.text_id === "grenade_low");
|
||
|
||
if (!grenade) {
|
||
logs(ctx, "Граната не найдена", { telegramId: ctx.from.id });
|
||
return ctx.reply("У вас нет гранаты для использования.");
|
||
}
|
||
|
||
const activeBattles = await Battle.findAll({
|
||
where: { location: location.id, status: "inactive" },
|
||
});
|
||
|
||
if (activeBattles.length === 0) {
|
||
logs(ctx, "Нет активных врагов в локации", { location });
|
||
return ctx.reply("В этой локации нет активных врагов.");
|
||
}
|
||
|
||
// Урон гранаты
|
||
const damage =
|
||
grenade.text_id === "grenade_hard"
|
||
? 50
|
||
: grenade.text_id === "grenade_medium"
|
||
? 30
|
||
: 10;
|
||
|
||
let message = `💥 Вы использовали гранату (${grenade.text_id}) и нанесли ${damage} урона всем врагам!`;
|
||
|
||
for (const battle of activeBattles) {
|
||
battle.enemy_hp -= damage;
|
||
await addBattleLog(battle, `💣 Враг получил ${damage} урона от гранаты.`);
|
||
if (battle.enemy_hp <= 0) {
|
||
battle.status = "completed";
|
||
await addBattleLog(battle, "💀 Враг был убит гранатой.");
|
||
}
|
||
|
||
await battle.save({ fields: ["enemy_hp", "logs", "status"] });
|
||
}
|
||
|
||
// Удаляем гранату из инвентаря
|
||
await InventoryModel.destroy({ where: { id: grenade.id } });
|
||
|
||
logs(ctx, "Использование гранаты", { grenade, damage });
|
||
await ctx.answerCbQuery(message, { show_alert: true });
|
||
});
|
||
|
||
rpg.action(/start_battle_\d+/, async (ctx) => {
|
||
const battleId = ctx.match[0].split('_')[2]; // Получаем ID битвы
|
||
const battle = await Battle.findByPk(battleId);
|
||
const enemy = await Enemy.findByPk(battle.enemy);
|
||
|
||
if (!battle || battle.status !== 'inactive') {
|
||
logs(ctx, "Битва не найдена или уже активна", { battleId });
|
||
return ctx.reply("Этот враг уже сражается с другим игроком или сражение завершено.");
|
||
}
|
||
|
||
const existingBattle = await Battle.findOne({
|
||
where: { character: ctx.from.id, status: 'active' },
|
||
});
|
||
|
||
if (existingBattle) {
|
||
logs(ctx, "Игрок уже участвует в другой битве", { telegramId: ctx.from.id });
|
||
return ctx.reply("Вы уже участвуете в другом сражении!");
|
||
}
|
||
|
||
const character = await CharacterModel.findOne({ where: { telegram_id: ctx.from.id } });
|
||
if (!character) {
|
||
logs(ctx, "Персонаж не найден", { telegramId: ctx.from.id });
|
||
return ctx.reply("Ваш персонаж не найден. Создайте его, чтобы начать играть!");
|
||
}
|
||
|
||
battle.status = 'active';
|
||
battle.character = ctx.from.id;
|
||
await battle.save();
|
||
|
||
await startBattle(ctx, character, enemy, battle);
|
||
});
|
||
|
||
const startBattle = async (ctx, character, enemy, battle) => {
|
||
await ctx.reply(
|
||
`⚔️ Начинается сражение!\n\nВаш противник: ${enemy.name}\n🛡️ Уровень: ${enemy.level}\n❤️ Здоровье: ${enemy.hp}\n\nВы готовы?`,
|
||
Markup.inlineKeyboard([[{ text: "Атаковать", callback_data: `attack` }]])
|
||
);
|
||
logs(ctx, "Начало сражения", { enemy, battle });
|
||
};
|
||
|
||
const enemyTurn = async (ctx, character, battle) => {
|
||
const enemy = await Enemy.findByPk(battle.enemy);
|
||
|
||
const dodgeChance = Math.min(character.resilience * 0.05, 0.5);
|
||
const isDodged = Math.random() < dodgeChance;
|
||
|
||
if (isDodged) {
|
||
// Генерация кнопок
|
||
const buttons = await generateBattleButtons();
|
||
const keyboard = Markup.inlineKeyboard(buttons);
|
||
// Сообщение с информацией
|
||
let battleLogs = await addBattleLog(battle, `💨 ${character.name} уклонился от атаки ${enemy.name}!`);
|
||
// Логи битвы
|
||
let logMessage = battleLogs.slice(-5).map((log) => `• ${log}`).join("\n");
|
||
logs(ctx, "Уклонение от атаки", { enemy, battle });
|
||
return await ctx.editMessageText(
|
||
`⚔️ Сражение с ${enemy.name}\n\n` +
|
||
`❤️ Здоровье врага: ${battle.enemy_hp}/${enemy.hp}\n` +
|
||
`⚔️ Урон врага: ${enemy.damage}\n\n` +
|
||
`📜 Логи битвы:\n${logMessage || "Пока ничего не произошло."}\n\n` +
|
||
`🎯 Выберите цель для атаки:`,
|
||
keyboard
|
||
);
|
||
}
|
||
|
||
const damage = enemy.damage;
|
||
character.hp -= damage;
|
||
await character.save();
|
||
|
||
if (character.hp <= 0) {
|
||
battle.status = "inactive";
|
||
let battleLogs = await addBattleLog(battle, `💔 ${enemy.name} нанес ${character.name} ${damage} урона!\n\n${character.name} потерпел поражение от ${enemy.name}.`);
|
||
await battle.save({ fields: ["logs", "status"] });
|
||
// При проигрыше игрок потеряет деньги или экипированные предметы
|
||
const inventory = await InventoryModel.findAll({ where: { telegram_id: ctx.from.id } });
|
||
const equippedItems = inventory.filter((item) => item.equipped);
|
||
if (equippedItems.length) {
|
||
const randomItem = equippedItems[Math.floor(Math.random() * equippedItems.length)];
|
||
// Снимаем предмет и снимает эффекты с персонажа
|
||
if (randomItem) {
|
||
processEffects(character, randomItem.effectData, false);
|
||
await randomItem.destroy();
|
||
await addBattleLog(battle, `💼 ${character.name} потерял ${randomItem.name}.`);
|
||
}
|
||
} else {
|
||
// Теряет 10% от денег
|
||
const lostMoney = Math.floor(character.dirtymoney * 0.1);
|
||
character.dirtymoney -= lostMoney;
|
||
await addBattleLog(battle, `💰 ${character.name} потерял ${lostMoney} монет.`);
|
||
}
|
||
await character.save();
|
||
await battle.save({ fields: ["logs", "status"] });
|
||
let logMessage = battleLogs.slice(-5).map((log) => `• ${log}`).join("\n");
|
||
logs(ctx, "Поражение персонажа", { enemy, battle });
|
||
return await ctx.editMessageText( `⚔️ Вы проиграли!\n\n` + `💔 ${enemy.name} нанес ${character.name} ${damage} урона!\n\n` + `📜 Логи битвы:\n${logMessage || "Пока ничего не произошло."}`);
|
||
}
|
||
|
||
let battleLogs = await addBattleLog(battle, `💔 ${enemy.name} нанес ${character.name} ${damage} урона. У ${character.name} осталось ${character.hp} HP.`);
|
||
let logMessage = battleLogs.slice(-5).map((log) => `• ${log}`).join("\n");
|
||
logs(ctx, "Атака врага", { enemy, battle, damage });
|
||
// Генерация кнопок
|
||
const buttons = await generateBattleButtons();
|
||
const keyboard = Markup.inlineKeyboard(buttons);
|
||
console.log(buttons)
|
||
return await ctx.editMessageText(
|
||
`⚔️ Сражение с ${enemy.name}\n\n` +
|
||
`❤️ Здоровье врага: ${battle.enemy_hp}/${enemy.hp}\n` +
|
||
`⚔️ Урон врага: ${enemy.damage}\n\n` +
|
||
`📜 Логи битвы:\n${logMessage || "Пока ничего не произошло."}\n\n` +
|
||
`🎯 Выберите цель для атаки:`,
|
||
keyboard
|
||
);
|
||
};
|
||
|
||
async function generateBattleButtons() {
|
||
const buttons = [
|
||
{
|
||
text: "🗡️ Атака",
|
||
callback_data: "attack",
|
||
},
|
||
{
|
||
text: "🛡️ Защита",
|
||
callback_data: "defend",
|
||
},
|
||
];
|
||
|
||
return buttons.map((btn) => [btn]);
|
||
}
|
||
|
||
|
||
// Функция выпадения предмета с врага или локации с учетом аксессуаров и управляемого шанса
|
||
async function dropItem(character, enemyId, locationId) {
|
||
let items = [];
|
||
|
||
// Получаем аксессуары игрока
|
||
const playerAccessories = await InventoryModel.findAll({
|
||
where: { telegram_id: character.telegram_id, type: "accessory", equipped: true },
|
||
});
|
||
|
||
// Загружаем данные о враге или локации
|
||
let source = null;
|
||
if (enemyId) {
|
||
source = await Enemy.findByPk(enemyId);
|
||
if (!source) return null;
|
||
items = source.loot; // Лут из врага
|
||
} else if (locationId) {
|
||
source = await Location.findByPk(locationId);
|
||
if (!source) return null;
|
||
items = source.loot; // Лут из локации
|
||
} else {
|
||
return null; // Если нет ни врага, ни локации
|
||
}
|
||
|
||
// Функция для получения модификации шанса от аксессуаров
|
||
function getChanceModifier(playerAccessories) {
|
||
let modifier = 1;
|
||
for (const accessory of playerAccessories) {
|
||
modifier += accessory.effectData.chance || 0; // Добавляем к шансу эффект аксессуара
|
||
}
|
||
return modifier;
|
||
}
|
||
|
||
// Получаем модификатор шанса от аксессуаров
|
||
const chanceModifier = getChanceModifier(playerAccessories);
|
||
|
||
// Перебираем все предметы из лута врага или локации
|
||
let droppedItems = [];
|
||
|
||
for (const itemId of items) {
|
||
const item = await ItemsModel.findByPk(itemId);
|
||
if (!item) continue;
|
||
|
||
// Шанс выпадения предмета (от 0 до 1) и его модификация через аксессуары
|
||
let baseChance = item.dropChance; // Этот шанс хранится в самом предмете, от 0 до 1
|
||
let finalChance = baseChance * chanceModifier; // Применяем модификатор к базовому шансу
|
||
|
||
// Ограничиваем шанс в пределах от 0 до 1
|
||
finalChance = Math.min(1, Math.max(0, finalChance));
|
||
|
||
// Генерация случайного числа для проверки выпадения
|
||
const dropRoll = Math.random(); // Случайное число от 0 до 1
|
||
|
||
// Проверка, выпал ли предмет
|
||
if (dropRoll <= finalChance) {
|
||
droppedItems.push(item); // Добавляем выпавший предмет в массив
|
||
console.log(`Предмет ${item.name} (id: ${item.id}) выпал с шансом ${finalChance * 100}%`);
|
||
}
|
||
}
|
||
|
||
// Теперь среди выпавших рандомом выбираем треть предметов (минимум 1)
|
||
const itemsToDrop = Math.max(1, Math.floor(droppedItems.length / 3));
|
||
droppedItems = droppedItems.sort(() => Math.random() - 0.5).slice(0, itemsToDrop);
|
||
console.log(`${droppedItems.length} предметов выпало`);
|
||
|
||
// Если предметы выпали, возвращаем их, иначе null
|
||
if (droppedItems.length > 0) {
|
||
// Отправляем сообщение игроку о найденных предметах
|
||
return droppedItems; // Вернем массив выпавших предметов
|
||
} else {
|
||
return null; // Если ничего не выпало
|
||
}
|
||
}
|
||
|
||
// Функция dropItem, но с возможностью передать врага или локацию напрямую для быстрого тестирования
|
||
async function dropItemFast(character, inventory, items) {
|
||
|
||
// Получаем аксессуары игрока
|
||
const playerAccessories = inventory.filter((item) => item.type === "accessory" && item.equipped);
|
||
|
||
|
||
// Функция для получения модификации шанса от аксессуаров
|
||
function getChanceModifier(playerAccessories) {
|
||
let modifier = 1;
|
||
for (const accessory of playerAccessories) {
|
||
modifier += accessory.effectData.chance || 0; // Добавляем к шансу эффект аксессуара
|
||
}
|
||
return modifier;
|
||
}
|
||
|
||
// Получаем модификатор шанса от аксессуаров
|
||
const chanceModifier = getChanceModifier(playerAccessories);
|
||
|
||
// Перебираем все предметы из лута врага или локации
|
||
let droppedItems = [];
|
||
|
||
for (const item of items) {
|
||
|
||
// Шанс выпадения предмета (от 0 до 1) и его модификация через аксессуары
|
||
let baseChance = item.dropChance; // Этот шанс хранится в самом предмете, от 0 до 1
|
||
let finalChance = baseChance * chanceModifier; // Применяем модификатор к базовому шансу
|
||
|
||
// Ограничиваем шанс в пределах от 0 до 1
|
||
finalChance = Math.min(1, Math.max(0, finalChance));
|
||
|
||
// Генерация случайного числа для проверки выпадения
|
||
const dropRoll = Math.random(); // Случайное число от 0 до 1
|
||
|
||
// Проверка, выпал ли предмет
|
||
if (dropRoll <= finalChance) {
|
||
droppedItems.push(item); // Добавляем выпавший предмет в массив
|
||
console.log(`Предмет ${item.name} (id: ${item.id}) выпал с шансом ${finalChance * 100}%`);
|
||
}
|
||
}
|
||
|
||
// Если предметы выпали, возвращаем их, иначе null
|
||
if (droppedItems.length > 0) {
|
||
rpg.sendMessage(character.telegram_id, `🎉 Вы нашли: ${droppedItems.map((item) => item.name).join(", ")}`);
|
||
return droppedItems; // Вернем массив выпавших предметов
|
||
} else {
|
||
return null; // Если ничего не выпало
|
||
}
|
||
}
|
||
|
||
// Запускаем быструю функцию dropItemFast для тестирования и передаем в нее уже готовые данные character, inventory и массив items
|
||
async function testDropItemFast() {
|
||
let character = await CharacterModel.findByPk(275416286);
|
||
let inventory = await InventoryModel.findAll({ where: { telegram_id: character.telegram_id } });
|
||
let items = await ItemsModel.findAll();
|
||
// Запускаем тест из 100 попыток и выводим подробную статистику
|
||
let itemsCount = {};
|
||
for (let i = 0; i < 10000; i++) {
|
||
const droppedItems = await dropItemFast(character, inventory, items);
|
||
if (!droppedItems) continue;
|
||
for (const item of droppedItems) {
|
||
itemsCount[item.rarity] = itemsCount[item.rarity] ? itemsCount[item.rarity] + 1 : 1;
|
||
}
|
||
}
|
||
console.log(itemsCount);
|
||
}
|
||
|
||
//setTimeout(testDropItemFast, 3000);
|
||
|
||
rpg.action("attack", async (ctx) => {
|
||
const character = await CharacterModel.findOne({ where: { telegram_id: ctx.from.id } });
|
||
const battle = await Battle.findOne({ where: { character: character.telegram_id, status: "active" } });
|
||
const enemy = await Enemy.findByPk(battle.enemy);
|
||
|
||
if (!character || !battle || !enemy) {
|
||
return ctx.reply("Ошибка: персонаж или битва не найдены.");
|
||
}
|
||
|
||
// Расчет шансов
|
||
const hitChance = 50 + character.endurance / 10; // Шанс попадания
|
||
const critChance = character.intelligence / 20; // Шанс крита
|
||
const critMultiplier = 1.5 + character.force / 100; // Критический урон
|
||
|
||
// Генерация результата атаки
|
||
const hitRoll = Math.random() * 100;
|
||
let damage = 0;
|
||
let result = "промах";
|
||
|
||
if (hitRoll <= hitChance) {
|
||
// Попадание
|
||
if (Math.random() * 100 <= critChance) {
|
||
damage = Math.floor(character.force * critMultiplier); // Критический удар
|
||
result = "критический удар";
|
||
} else {
|
||
damage = character.force; // Обычная атака
|
||
result = "попадание";
|
||
}
|
||
battle.enemy_hp -= damage;
|
||
} else {
|
||
// Промах — враг делает ход
|
||
return await enemyTurn(ctx, character, battle);
|
||
}
|
||
|
||
// Логи боя
|
||
let battleLogs = await addBattleLog(battle, `${character.name} нанес ${damage} урона (${result}).`);
|
||
await battle.save();
|
||
|
||
// Проверка на победу
|
||
if (battle.enemy_hp <= 0) {
|
||
battle.status = "completed";
|
||
await battle.save();
|
||
let items = '';
|
||
Exp(ctx, character, enemy.level * 6)
|
||
character.enemiesKilled += 1;
|
||
await character.save();
|
||
let droppedItems = await dropItem(character, enemy.id);
|
||
logs(ctx, "Предметы с врага", { droppedItems });
|
||
if (droppedItems) {
|
||
for (const item of droppedItems) {
|
||
await InventoryModel.create({
|
||
telegram_id: character.telegram_id,
|
||
name: item.name,
|
||
text_id: item.text_id,
|
||
description: item.description,
|
||
effectData: item.effectData,
|
||
price: item.price,
|
||
rarity: item.rarity,
|
||
type: item.type,
|
||
duration: item.duration,
|
||
canBeEquipped: item.canBeEquipped,
|
||
equipped: false,
|
||
img: item.img
|
||
});
|
||
}
|
||
items = droppedItems.map((item) => item.name).join(", ");
|
||
}
|
||
let logMessage = battleLogs.slice(-5).map((log) => `• ${log}`).join("\n");
|
||
return ctx.editMessageText(
|
||
`🎉 ${character.name} победил ${enemy.name}!\n\n📜 Логи битвы:\n${logMessage}\n🧩 Выпавшие предметы: ${items}`
|
||
);
|
||
}
|
||
|
||
// Продолжение боя
|
||
let logMessage = battleLogs.slice(-5).map((log) => `• ${log}`).join("\n");
|
||
const buttons = await generateBattleButtons();
|
||
const keyboard = Markup.inlineKeyboard(buttons);
|
||
|
||
await ctx.editMessageText(
|
||
`⚔️ Сражение с ${enemy.name}\n\n` +
|
||
`❤️ Здоровье врага: ${battle.enemy_hp}/${enemy.hp}\n` +
|
||
`📜 Логи битвы:\n${logMessage}\n\n` +
|
||
`🎯 Доступные действия:`,
|
||
keyboard
|
||
);
|
||
});
|
||
|
||
async function addBattleLog(battle, logMessage) {
|
||
if (!battle.logs) battle.logs = []; // Инициализация массива, если он пустой
|
||
// Добавляем перед сообщением дату и время до миллисекунды
|
||
logMessage = `[${new Date().toISOString()}] ${logMessage}`;
|
||
battle.logs = [...battle.logs, logMessage]; // Добавляем сообщение в массив логов
|
||
await battle.save({ fields: ["logs"] }); // Сохранение изменений
|
||
return battle.logs; // Возвращаем обновленный массив логов
|
||
}
|
||
|
||
|
||
rpg.command('checkstats', async (ctx) => {
|
||
const telegramId = ctx.from.id;
|
||
|
||
// Ищем персонажа
|
||
const character = await CharacterModel.findOne({ where: { telegram_id: telegramId } });
|
||
|
||
if (!character) {
|
||
return ctx.reply('Персонаж не найден. Создайте нового персонажа, чтобы начать игру!');
|
||
}
|
||
const hitChance = Math.min(90, 50 + character.endurance / 10);
|
||
const critChance = Math.min(90, character.intelligence / 20);
|
||
const critMultiplier = 1.5 + character.force / 100;
|
||
// Формируем профиль
|
||
const profile = `
|
||
🎭 Профиль персонажа
|
||
|
||
👤 Имя: ${character.name || 'Не указано'}
|
||
🏆 Уровень: ${character.level} (${character.exp}/${utils.spaces(expToUp[character.level])})
|
||
|
||
❤️ Здоровье: ${character.hp}/${character.max_hp}
|
||
🔥 Стамина: ${character.stamina}/${character.max_stamina}
|
||
|
||
💪 Сила (F): ${character.force}
|
||
🧠 Интеллект (I): ${character.intelligence}
|
||
🛡️ Устойчивость (R): ${character.resilience}
|
||
🔋 Выносливость (E): ${character.endurance}
|
||
|
||
🎯 Шанс попадания: ${hitChance.toFixed(1)}%
|
||
🔥 Шанс крита: ${critChance.toFixed(1)}%
|
||
💥 Критический урон: ${(critMultiplier * 100).toFixed(1)}%
|
||
|
||
`;
|
||
// Отправляем сообщение
|
||
ctx.reply(profile.trim());
|
||
});
|
||
|
||
module.exports = rpg;
|