Item Drop Function
Images for Items
This commit is contained in:
Degradin 2025-01-20 18:52:53 +03:00
parent 1ce3218559
commit a9f1337bbe
28 changed files with 17081 additions and 16 deletions

File diff suppressed because it is too large Load Diff

BIN
media/items/armor_cloth.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

BIN
media/items/big_aid_kit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

BIN
media/items/boots_army.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

BIN
media/items/grenade_low.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

BIN
media/items/helmet_moto.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

BIN
media/items/image (12).png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 KiB

BIN
media/items/knife.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

BIN
media/items/simcard.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

BIN
media/items/template.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

View File

@ -56,6 +56,11 @@ const Inventory = sequelize.define('inventory', {
type: DataTypes.BOOLEAN,
defaultValue: false
},
img: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: faker.image.url()
},
});
module.exports = Inventory;

View File

@ -33,6 +33,11 @@ const Item = sequelize.define('item', {
type: DataTypes.INTEGER,
allowNull: false,
},
dropChance: {
type: DataTypes.FLOAT,
allowNull: false,
defaultValue: 1,
},
type: {
type: DataTypes.STRING, // Тип предмета (например, "инструмент", "ресурс")
allowNull: false,
@ -45,6 +50,11 @@ const Item = sequelize.define('item', {
type: DataTypes.BOOLEAN,
defaultValue: false
},
img: {
type: DataTypes.STRING,
allowNull: true,
defaultValue: faker.image.url()
},
});
module.exports = Item;

445
rpg.js
View File

@ -1,5 +1,7 @@
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');
@ -8,6 +10,7 @@ const sequelize = require('./db'); // Подключение базы данны
const utils = require('./utils');
const handlers = require('./handlers');
const { keyboard } = require('telegraf/markup');
const { parse } = require('error-stack-parser');
const {
phones,
expToUp,
@ -973,6 +976,29 @@ rpg.action(/lock_*/, async (ctx) => {
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);
@ -997,7 +1023,7 @@ rpg.action('inventory', async (ctx) => {
if (equippedItems.length > 0) {
message += '🛡️ Снаряженные предметы:\n';
equippedItems.forEach((item) => {
message += `- ${item.name} (${item.type})\n`;
message += `- ${item.name} (${getItemType(item.type)})\n`;
});
message += '\n';
}
@ -1005,7 +1031,7 @@ rpg.action('inventory', async (ctx) => {
if (unequippedItems.length > 0) {
message += '🎒 Предметы в инвентаре:\n';
unequippedItems.forEach((item) => {
message += `- ${item.name} (${item.type})\n`;
message += `- ${item.name} (${getItemType(item.type)})\n`;
});
message += '\n';
}
@ -1044,17 +1070,46 @@ rpg.action(/view_item_(\d+)/, async (ctx) => {
if (item.equipped) {
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) {
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) {
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) => {
@ -1079,6 +1134,15 @@ rpg.action(/use_item_(\d+)/, async (ctx) => {
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) {
const resultMessages = processEffects(character, item.effectData, true);
@ -1191,13 +1255,50 @@ rpg.action(/buy_item_(\d+)/, async (ctx) => {
type: item.type,
duration: item.duration,
canBeEquipped: item.canBeEquipped,
equipped: false
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);
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) {
@ -1275,14 +1376,50 @@ const processEffects = (character, effects, isEquipping) => {
case 'stamina_penalty':
character.max_stamina += effect.amount;
character.save()
messages.push(`Ваша выносливость увеличена на ${effect.amount}.`);
messages.push(`Ваша стамина увеличена на ${effect.amount}.`);
break;
case 'intelligence_boost':
character.intelligence -= Math.max(0, character.intelligence - effect.amount);
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('Неизвестный эффект при снятии.');
@ -1311,7 +1448,7 @@ const processEffects = (character, effects, isEquipping) => {
case 'stamina_penalty':
character.max_stamina -= effect.amount;
character.save()
messages.push(`Ваша выносливость уменьшена на ${effect.amount}.`);
messages.push(`Ваша стамина уменьшена на ${effect.amount}.`);
break;
case 'stamina_recover':
@ -1326,6 +1463,42 @@ const processEffects = (character, effects, isEquipping) => {
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('Неизвестный эффект при снаряжении.');
}
@ -1335,6 +1508,70 @@ const processEffects = (character, effects, isEquipping) => {
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 = [];
@ -1787,14 +2024,28 @@ const startBattle = async (ctx, character, enemy, battle) => {
const enemyTurn = async (ctx, character, battle) => {
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 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) {
// Сообщение с информацией
battle.logs.push(`💨 ${character.name} уклонились от атаки противника!`);
await battle.save({ fields: ["logs"] });
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;
@ -1805,6 +2056,23 @@ const enemyTurn = async (ctx, character, battle) => {
battle.logs.push(
`💔 Противник нанес ${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"] });
logs(ctx, "Поражение персонажа", { enemy, battle });
return;
@ -1814,6 +2082,14 @@ const enemyTurn = async (ctx, character, battle) => {
battle.logs.push(`💔 Противник нанес ${character.name} ${damage} урона. У ${character.name} осталось ${character.hp} HP.`);
await battle.save({ fields: ["logs"] });
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) => {
@ -1960,6 +2236,143 @@ function generateBattleButtons(character, battle) {
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) {
logs(ctx, "Поражение персонажа", { enemy, battle });
return ctx.editMessageText(
`💔 ${character.name} потерпел поражение от ${enemy.name}!\n\n📜 Логи битвы:\n${logs}`
`💔 ${character.name} потерпел поражение от ${enemy.name}!\n\n📜 Логи битвы:\n${logMessage}`
);
}
logs(ctx, "Промах", { enemy, battle });