v5.9.7
Item Drop Function Images for Items
16637
json/logs.json
BIN
media/items/armor_cloth.png
Normal file
After Width: | Height: | Size: 242 KiB |
BIN
media/items/armor_leather.png
Normal file
After Width: | Height: | Size: 281 KiB |
BIN
media/items/armor_neiroshield.png
Normal file
After Width: | Height: | Size: 284 KiB |
BIN
media/items/baseball_bat.png
Normal file
After Width: | Height: | Size: 154 KiB |
BIN
media/items/big_aid_kit.png
Normal file
After Width: | Height: | Size: 256 KiB |
BIN
media/items/boots_army.png
Normal file
After Width: | Height: | Size: 335 KiB |
BIN
media/items/cardreader_emulator.png
Normal file
After Width: | Height: | Size: 204 KiB |
BIN
media/items/drink_litenergy.png
Normal file
After Width: | Height: | Size: 261 KiB |
BIN
media/items/drink_redbull.png
Normal file
After Width: | Height: | Size: 256 KiB |
BIN
media/items/drink_tornadoenergy.png
Normal file
After Width: | Height: | Size: 215 KiB |
BIN
media/items/glasses_botanic.png
Normal file
After Width: | Height: | Size: 187 KiB |
BIN
media/items/grenade_low.png
Normal file
After Width: | Height: | Size: 181 KiB |
BIN
media/items/helmet_moto.png
Normal file
After Width: | Height: | Size: 225 KiB |
BIN
media/items/image (12).png
Normal file
After Width: | Height: | Size: 291 KiB |
BIN
media/items/implant_vaultone.png
Normal file
After Width: | Height: | Size: 261 KiB |
BIN
media/items/knife.png
Normal file
After Width: | Height: | Size: 166 KiB |
BIN
media/items/knuckle_basic.png
Normal file
After Width: | Height: | Size: 201 KiB |
BIN
media/items/medium_aid_kit.png
Normal file
After Width: | Height: | Size: 168 KiB |
BIN
media/items/pistol_pioneer.png
Normal file
After Width: | Height: | Size: 230 KiB |
BIN
media/items/pistol_regular.png
Normal file
After Width: | Height: | Size: 232 KiB |
BIN
media/items/simcard.png
Normal file
After Width: | Height: | Size: 152 KiB |
BIN
media/items/small_aid_kit.png
Normal file
After Width: | Height: | Size: 107 KiB |
BIN
media/items/stimulator_25.png
Normal file
After Width: | Height: | Size: 186 KiB |
BIN
media/items/template.png
Normal file
After Width: | Height: | Size: 133 KiB |
@ -56,6 +56,11 @@ const Inventory = sequelize.define('inventory', {
|
|||||||
type: DataTypes.BOOLEAN,
|
type: DataTypes.BOOLEAN,
|
||||||
defaultValue: false
|
defaultValue: false
|
||||||
},
|
},
|
||||||
|
img: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: faker.image.url()
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = Inventory;
|
module.exports = Inventory;
|
||||||
|
@ -33,6 +33,11 @@ const Item = sequelize.define('item', {
|
|||||||
type: DataTypes.INTEGER,
|
type: DataTypes.INTEGER,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
},
|
},
|
||||||
|
dropChance: {
|
||||||
|
type: DataTypes.FLOAT,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: 1,
|
||||||
|
},
|
||||||
type: {
|
type: {
|
||||||
type: DataTypes.STRING, // Тип предмета (например, "инструмент", "ресурс")
|
type: DataTypes.STRING, // Тип предмета (например, "инструмент", "ресурс")
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
@ -45,6 +50,11 @@ const Item = sequelize.define('item', {
|
|||||||
type: DataTypes.BOOLEAN,
|
type: DataTypes.BOOLEAN,
|
||||||
defaultValue: false
|
defaultValue: false
|
||||||
},
|
},
|
||||||
|
img: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: true,
|
||||||
|
defaultValue: faker.image.url()
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = Item;
|
module.exports = Item;
|
||||||
|
445
rpg.js
@ -1,5 +1,7 @@
|
|||||||
const { Telegraf, Scenes, session, Markup, Stage, Composer } = require('telegraf');
|
const { Telegraf, Scenes, session, Markup, Stage, Composer } = require('telegraf');
|
||||||
const schedule = require('node-schedule');
|
const schedule = require('node-schedule');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
const { faker, fakerRU } = require('@faker-js/faker');
|
const { faker, fakerRU } = require('@faker-js/faker');
|
||||||
// Подключаем необходимые библиотеки
|
// Подключаем необходимые библиотеки
|
||||||
const { Op } = require('sequelize');
|
const { Op } = require('sequelize');
|
||||||
@ -8,6 +10,7 @@ const sequelize = require('./db'); // Подключение базы данны
|
|||||||
const utils = require('./utils');
|
const utils = require('./utils');
|
||||||
const handlers = require('./handlers');
|
const handlers = require('./handlers');
|
||||||
const { keyboard } = require('telegraf/markup');
|
const { keyboard } = require('telegraf/markup');
|
||||||
|
const { parse } = require('error-stack-parser');
|
||||||
const {
|
const {
|
||||||
phones,
|
phones,
|
||||||
expToUp,
|
expToUp,
|
||||||
@ -973,6 +976,29 @@ rpg.action(/lock_*/, async (ctx) => {
|
|||||||
removeButton(ctx, buttonId);
|
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) => {
|
rpg.action('inventory', async (ctx) => {
|
||||||
try {
|
try {
|
||||||
const character = await CharacterModel.findByPk(ctx.from.id);
|
const character = await CharacterModel.findByPk(ctx.from.id);
|
||||||
@ -997,7 +1023,7 @@ rpg.action('inventory', async (ctx) => {
|
|||||||
if (equippedItems.length > 0) {
|
if (equippedItems.length > 0) {
|
||||||
message += '🛡️ Снаряженные предметы:\n';
|
message += '🛡️ Снаряженные предметы:\n';
|
||||||
equippedItems.forEach((item) => {
|
equippedItems.forEach((item) => {
|
||||||
message += `- ${item.name} (${item.type})\n`;
|
message += `- ${item.name} (${getItemType(item.type)})\n`;
|
||||||
});
|
});
|
||||||
message += '\n';
|
message += '\n';
|
||||||
}
|
}
|
||||||
@ -1005,7 +1031,7 @@ rpg.action('inventory', async (ctx) => {
|
|||||||
if (unequippedItems.length > 0) {
|
if (unequippedItems.length > 0) {
|
||||||
message += '🎒 Предметы в инвентаре:\n';
|
message += '🎒 Предметы в инвентаре:\n';
|
||||||
unequippedItems.forEach((item) => {
|
unequippedItems.forEach((item) => {
|
||||||
message += `- ${item.name} (${item.type})\n`;
|
message += `- ${item.name} (${getItemType(item.type)})\n`;
|
||||||
});
|
});
|
||||||
message += '\n';
|
message += '\n';
|
||||||
}
|
}
|
||||||
@ -1044,17 +1070,46 @@ rpg.action(/view_item_(\d+)/, async (ctx) => {
|
|||||||
if (item.equipped) {
|
if (item.equipped) {
|
||||||
return ctx.reply(`${item.name} уже снаряжен.`);
|
return ctx.reply(`${item.name} уже снаряжен.`);
|
||||||
}
|
}
|
||||||
const buttons = []
|
|
||||||
let message = `ℹ️ ${item.name}\n`
|
// Подготовка кнопок
|
||||||
message += ` 📜 ${item.description}\n\n 〰️Редкость: ${item.rarity}\n 🔤Тип: ${item.type}`
|
const buttons = [];
|
||||||
if (!item.equipped) {
|
if (!item.equipped) {
|
||||||
buttons.push([Markup.button.callback(`🎯 Использовать ${item.name}`, `use_item_${item.id}`)]);
|
buttons.push({ text: `🎯 Использовать ${item.name}`, callback_data: `use_item_${item.id}` });
|
||||||
}
|
}
|
||||||
if (item.equipped && item.canBeEquipped) {
|
if (item.equipped && item.canBeEquipped) {
|
||||||
buttons.push([Markup.button.callback(`🚫 Снять ${item.name}`, `unequip_item_${item.id}`)]);
|
buttons.push({ text: `🚫 Снять ${item.name}`, callback_data: `unequip_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'); // Путь к картинке
|
||||||
|
console.log(imagePath);
|
||||||
|
|
||||||
|
// Проверяем существует ли файл
|
||||||
|
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('Произошла ошибка при отправке сообщения.');
|
||||||
}
|
}
|
||||||
logs(ctx, "Просмотр предмета", {item: item});
|
|
||||||
await ctx.reply(message, Markup.inlineKeyboard(buttons, { columns: 2 }));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
rpg.action(/use_item_(\d+)/, async (ctx) => {
|
rpg.action(/use_item_(\d+)/, async (ctx) => {
|
||||||
@ -1079,6 +1134,15 @@ rpg.action(/use_item_(\d+)/, async (ctx) => {
|
|||||||
return ctx.answerCbQuery(`🚫 ${item.name} нельзя сейчас использовать.`);
|
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}, пока надеты другие предметы того же типа.`);
|
||||||
|
}
|
||||||
|
|
||||||
// Применяем эффекты предмета
|
// Применяем эффекты предмета
|
||||||
if (item.effectData) {
|
if (item.effectData) {
|
||||||
const resultMessages = processEffects(character, item.effectData, true);
|
const resultMessages = processEffects(character, item.effectData, true);
|
||||||
@ -1191,13 +1255,50 @@ rpg.action(/buy_item_(\d+)/, async (ctx) => {
|
|||||||
type: item.type,
|
type: item.type,
|
||||||
duration: item.duration,
|
duration: item.duration,
|
||||||
canBeEquipped: item.canBeEquipped,
|
canBeEquipped: item.canBeEquipped,
|
||||||
equipped: false
|
equipped: false,
|
||||||
|
img: item.img
|
||||||
});
|
});
|
||||||
|
|
||||||
await character.save();
|
await character.save();
|
||||||
ctx.answerCbQuery(`Вы успешно купили ${item.name} за ${item.price}₽!`, { show_alert: true });
|
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);
|
||||||
|
console.log(itemId)
|
||||||
|
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) {
|
CharacterModel.prototype.addEquippedItem = async function (item) {
|
||||||
@ -1275,14 +1376,50 @@ const processEffects = (character, effects, isEquipping) => {
|
|||||||
case 'stamina_penalty':
|
case 'stamina_penalty':
|
||||||
character.max_stamina += effect.amount;
|
character.max_stamina += effect.amount;
|
||||||
character.save()
|
character.save()
|
||||||
messages.push(`Ваша выносливость увеличена на ${effect.amount}.`);
|
messages.push(`Ваша стамина увеличена на ${effect.amount}.`);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'intelligence_boost':
|
case 'intelligence_boost':
|
||||||
character.intelligence -= Math.max(0, character.intelligence - effect.amount);
|
character.intelligence = Math.max(0, character.intelligence - effect.amount);
|
||||||
character.save()
|
character.save()
|
||||||
messages.push(`Ваш интеллект уменьшена на ${effect.amount}.`);
|
messages.push(`Ваш интеллект уменьшена на ${effect.amount}.`);
|
||||||
break;
|
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:
|
default:
|
||||||
messages.push('Неизвестный эффект при снятии.');
|
messages.push('Неизвестный эффект при снятии.');
|
||||||
@ -1311,7 +1448,7 @@ const processEffects = (character, effects, isEquipping) => {
|
|||||||
case 'stamina_penalty':
|
case 'stamina_penalty':
|
||||||
character.max_stamina -= effect.amount;
|
character.max_stamina -= effect.amount;
|
||||||
character.save()
|
character.save()
|
||||||
messages.push(`Ваша выносливость уменьшена на ${effect.amount}.`);
|
messages.push(`Ваша стамина уменьшена на ${effect.amount}.`);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'stamina_recover':
|
case 'stamina_recover':
|
||||||
@ -1326,6 +1463,42 @@ const processEffects = (character, effects, isEquipping) => {
|
|||||||
messages.push(`Ваш интеллект увеличен на ${effect.amount}.`);
|
messages.push(`Ваш интеллект увеличен на ${effect.amount}.`);
|
||||||
break;
|
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:
|
default:
|
||||||
messages.push('Неизвестный эффект при снаряжении.');
|
messages.push('Неизвестный эффект при снаряжении.');
|
||||||
}
|
}
|
||||||
@ -1335,6 +1508,70 @@ const processEffects = (character, effects, isEquipping) => {
|
|||||||
return messages.join('\n');
|
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() {
|
function generateKeyboard() {
|
||||||
const buttonsCount = 10;
|
const buttonsCount = 10;
|
||||||
const buttons = [];
|
const buttons = [];
|
||||||
@ -1787,14 +2024,28 @@ const startBattle = async (ctx, character, enemy, battle) => {
|
|||||||
const enemyTurn = async (ctx, character, battle) => {
|
const enemyTurn = async (ctx, character, battle) => {
|
||||||
const enemy = await Enemy.findByPk(battle.enemy);
|
const enemy = await Enemy.findByPk(battle.enemy);
|
||||||
|
|
||||||
const dodgeChance = Math.min(character.resistance * 0.05, 0.5);
|
const dodgeChance = Math.min(character.resilience * 0.05, 0.5);
|
||||||
const isDodged = Math.random() < dodgeChance;
|
const isDodged = Math.random() < dodgeChance;
|
||||||
|
// Генерация кнопок
|
||||||
|
const buttons = generateBattleButtons(character, battle);
|
||||||
|
const keyboard = Markup.inlineKeyboard(buttons);
|
||||||
|
// Логи битвы
|
||||||
|
const battlelogs = battle.logs || [];
|
||||||
|
const logMessage = battlelogs.slice(-5).map((log) => `• ${log}`).join("\n");
|
||||||
|
|
||||||
if (isDodged) {
|
if (isDodged) {
|
||||||
|
// Сообщение с информацией
|
||||||
battle.logs.push(`💨 ${character.name} уклонились от атаки противника!`);
|
battle.logs.push(`💨 ${character.name} уклонились от атаки противника!`);
|
||||||
await battle.save({ fields: ["logs"] });
|
await battle.save({ fields: ["logs"] });
|
||||||
logs(ctx, "Уклонение от атаки", { enemy, battle });
|
logs(ctx, "Уклонение от атаки", { enemy, battle });
|
||||||
return;
|
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;
|
const damage = enemy.damage;
|
||||||
@ -1805,6 +2056,23 @@ const enemyTurn = async (ctx, character, battle) => {
|
|||||||
battle.logs.push(
|
battle.logs.push(
|
||||||
`💔 Противник нанес ${character.name} ${damage} урона!\n\n${character.name} потерпел поражение от ${enemy.name}.`
|
`💔 Противник нанес ${character.name} ${damage} урона!\n\n${character.name} потерпел поражение от ${enemy.name}.`
|
||||||
);
|
);
|
||||||
|
// При проигрыше игрок потеряет деньги или экипированные предметы
|
||||||
|
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)];
|
||||||
|
// Снимаем предмет и снимает эффекты с персонажа
|
||||||
|
processEffects(character, randomItem.effectData, false);
|
||||||
|
randomItem.equipped = false;
|
||||||
|
await randomItem.destroy();
|
||||||
|
battle.logs.push(`💼 ${character.name} потерял ${randomItem.name}.`);
|
||||||
|
} else {
|
||||||
|
// Теряет 10% от денег
|
||||||
|
const lostMoney = Math.floor(character.dirtymoney * 0.1);
|
||||||
|
character.dirtymoney -= lostMoney;
|
||||||
|
battle.logs.push(`💰 ${character.name} потерял ${lostMoney} монет.`);
|
||||||
|
}
|
||||||
|
await character.save();
|
||||||
await battle.save({ fields: ["logs", "status"] });
|
await battle.save({ fields: ["logs", "status"] });
|
||||||
logs(ctx, "Поражение персонажа", { enemy, battle });
|
logs(ctx, "Поражение персонажа", { enemy, battle });
|
||||||
return;
|
return;
|
||||||
@ -1814,6 +2082,14 @@ const enemyTurn = async (ctx, character, battle) => {
|
|||||||
battle.logs.push(`💔 Противник нанес ${character.name} ${damage} урона. У ${character.name} осталось ${character.hp} HP.`);
|
battle.logs.push(`💔 Противник нанес ${character.name} ${damage} урона. У ${character.name} осталось ${character.hp} HP.`);
|
||||||
await battle.save({ fields: ["logs"] });
|
await battle.save({ fields: ["logs"] });
|
||||||
logs(ctx, "Атака врага", { enemy, battle, damage });
|
logs(ctx, "Атака врага", { enemy, battle, damage });
|
||||||
|
return await ctx.editMessageText(
|
||||||
|
`⚔️ Сражение с ${enemy.name}\n\n` +
|
||||||
|
`❤️ Здоровье врага: ${battle.enemy_hp}/${enemy.hp}\n` +
|
||||||
|
`⚔️ Урон врага: ${enemy.damage}\n\n` +
|
||||||
|
`📜 Логи битвы:\n${logMessage || "Пока ничего не произошло."}\n\n` +
|
||||||
|
`🎯 Выберите цель для атаки:`,
|
||||||
|
keyboard
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
rpg.action(/attack_\d+/, async (ctx) => {
|
rpg.action(/attack_\d+/, async (ctx) => {
|
||||||
@ -1960,6 +2236,143 @@ function generateBattleButtons(character, battle) {
|
|||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Функция выпадения предмета с врага или локации с учетом аксессуаров и управляемого шанса
|
||||||
|
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) {
|
||||||
|
console.log('enemyid')
|
||||||
|
source = await Enemy.findByPk(enemyId);
|
||||||
|
if (!source) return null;
|
||||||
|
items = source.loot; // Лут из врага
|
||||||
|
} else if (locationId) {
|
||||||
|
console.log('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}%`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если предметы выпали, возвращаем их, иначе 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) {
|
||||||
|
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);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Обработка критического удара
|
// Обработка критического удара
|
||||||
@ -2050,7 +2463,7 @@ rpg.action(/miss_\d+/, async (ctx) => {
|
|||||||
if (character.hp <= 0) {
|
if (character.hp <= 0) {
|
||||||
logs(ctx, "Поражение персонажа", { enemy, battle });
|
logs(ctx, "Поражение персонажа", { enemy, battle });
|
||||||
return ctx.editMessageText(
|
return ctx.editMessageText(
|
||||||
`💔 ${character.name} потерпел поражение от ${enemy.name}!\n\n📜 Логи битвы:\n${logs}`
|
`💔 ${character.name} потерпел поражение от ${enemy.name}!\n\n📜 Логи битвы:\n${logMessage}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
logs(ctx, "Промах", { enemy, battle });
|
logs(ctx, "Промах", { enemy, battle });
|
||||||
|