From b4087cf26c551c92c2a7dad5472dfbd4dbca9bf3 Mon Sep 17 00:00:00 2001 From: Degradin Date: Sun, 12 Jan 2025 03:37:21 +0300 Subject: [PATCH] v5.5 Battle Update Prepare --- config/index.js | 3 + models/battle.model.js | 33 +++ models/enemy.model.js | 40 ++++ models/location.model.js | 38 ++++ rpg.js | 470 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 584 insertions(+) create mode 100644 models/battle.model.js create mode 100644 models/enemy.model.js create mode 100644 models/location.model.js diff --git a/config/index.js b/config/index.js index b44dbd8..cea7dbe 100644 --- a/config/index.js +++ b/config/index.js @@ -19,6 +19,9 @@ module.exports = { CharacterModel : require('../models/character.model'), InventoryModel : require('../models/inventory.model'), ItemsModel : require('../models/items.model'), + Enemy : require('../models/enemy.model'), + Location : require('../models/location.model'), + Battle : require('../models/battle.model'), StolenCardsModel : require('../models/stolencards.model'), WorldModel : require('../models/world.model'), JobModel : require('../models/job.model'), diff --git a/models/battle.model.js b/models/battle.model.js new file mode 100644 index 0000000..713d85d --- /dev/null +++ b/models/battle.model.js @@ -0,0 +1,33 @@ +const { DataTypes } = require('sequelize'); +const sequelize = require('../db'); + +const Battle = sequelize.define('battle', { + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true, + }, + enemy: { + type: DataTypes.INTEGER, + allowNull: true, + }, + location: { + type: DataTypes.INTEGER, + allowNull: true, + }, + character: { + type: DataTypes.BIGINT, + allowNull: true, + }, + enemy_hp: { + type: DataTypes.INTEGER, + allowNull: true, + }, + status: { + type: DataTypes.STRING, + allowNull: false, + defaultValue: "inactive" + }, +}); + +module.exports = Battle; diff --git a/models/enemy.model.js b/models/enemy.model.js new file mode 100644 index 0000000..8a217b3 --- /dev/null +++ b/models/enemy.model.js @@ -0,0 +1,40 @@ +const { DataTypes } = require('sequelize'); +const sequelize = require('../db'); + +const Enemy = sequelize.define('enemy', { + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true, + }, + name: { + type: DataTypes.STRING, + allowNull: false, + }, + description: { + type: DataTypes.TEXT, + allowNull: false, + }, + level: { + type: DataTypes.INTEGER, + allowNull: false, + }, + hp: { + type: DataTypes.INTEGER, + allowNull: true + }, + damage: { + type: DataTypes.INTEGER, + allowNull: false, + }, + loot: { + type: DataTypes.ARRAY(DataTypes.INTEGER), + allowNull: true, defaultValue: [] + }, + rarity: { + type: DataTypes.INTEGER, + allowNull: false, + }, +}); + +module.exports = Enemy; diff --git a/models/location.model.js b/models/location.model.js new file mode 100644 index 0000000..569fd75 --- /dev/null +++ b/models/location.model.js @@ -0,0 +1,38 @@ +const { DataTypes } = require('sequelize'); +const sequelize = require('../db'); + +const Location = sequelize.define('location', { + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true, + }, + name: { + type: DataTypes.STRING, + allowNull: false, + }, + description: { + type: DataTypes.TEXT, + allowNull: false, + }, + enemies: { + type: DataTypes.ARRAY(DataTypes.INTEGER), + allowNull: true, + defaultValue: [] + }, + level: { + type: DataTypes.INTEGER, + allowNull: false, + }, + loot: { + type: DataTypes.ARRAY(DataTypes.INTEGER), + allowNull: true, + defaultValue: [] + }, + rarity: { + type: DataTypes.INTEGER, + allowNull: false, + }, +}); + +module.exports = Location; diff --git a/rpg.js b/rpg.js index 6ff8b17..45e6bd3 100644 --- a/rpg.js +++ b/rpg.js @@ -14,6 +14,9 @@ const { CharacterModel, InventoryModel, ItemsModel, + Enemy, + Location, + Battle, StolenCardsModel, WorldModel, PropertyModel, @@ -1502,13 +1505,480 @@ startRecoveryIntervals() +const fillEnemies = async () => { + const enemies = [ + { name: "Бандит", description: "Обычный бандит с ножом", level: 1, hp: 50, damage: 10, loot: [1, 2], rarity: 1 }, + { name: "Вор", description: "Мелкий воришка", level: 1, hp: 30, damage: 5, loot: [3], rarity: 1 }, + { name: "Собака", description: "Голодная дворняга", level: 1, hp: 40, damage: 8, loot: [], rarity: 2 }, + { name: "Амбал-бандит", description: "Сильный и опасный бандит", level: 3, hp: 120, damage: 25, loot: [4, 5], rarity: 3 }, + ]; + + for (const enemy of enemies) { + await Enemy.create(enemy); + } + console.log("Enemies filled."); +}; + +const fillLocations = async () => { + const locations = [ + { + name: "Заброшенный склад", + description: "Темный склад с кучей мусора.", + enemies: [1, 2, 3], + level: 1, + loot: [6, 7], + rarity: 1 + }, + { + name: "Лесопосадка", + description: "Густой лес с опасными тропами.", + enemies: [2, 3, 4], + level: 2, + loot: [8, 9], + rarity: 2 + }, + ]; + + for (const location of locations) { + await Location.create(location); + } + console.log("Locations filled."); +}; + +const generateBattles = async () => { + const locations = await Location.findAll(); + + for (const location of locations) { + 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; + + await Battle.create({ + enemy: enemy.id, + location: location.id, + character: null, + enemy_hp: enemy.hp, + status: "inactive", + }); + } + } + console.log("Battles generated."); +}; + +rpg.command("locations", async (ctx) => { + const locations = await Location.findAll(); + if (!locations.length) { + return ctx.reply("Нет доступных локаций для исследования."); + } + + const locationButtons = locations.map((location) => ({ + text: location.name, + callback_data: `viewlocation_${location.id}`, + })); + + const keyboard = Markup.inlineKeyboard( + locationButtons.map((button) => [button]) // Одна кнопка на строку + ); + + 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.reply(description, keyboard); +}); + +rpg.action(/hunt_location_\d+/, async (ctx) => { + const locationId = ctx.match[0].split('_')[2]; // Получаем ID локации + const location = await Location.findByPk(locationId); + + if (!location) { + return ctx.reply("Локация не найдена."); + } + + const enemyIds = location.enemies || []; + if (enemyIds.length === 0) { + return ctx.reply("В этой локации сейчас нет врагов. Попробуйте позже."); + } + + const activeBattles = await Battle.findAll({ + where: { location: location.id, status: 'inactive' }, + }); + + if (activeBattles.length === 0) { + return ctx.reply("В этой локации сейчас нет активных врагов. Попробуйте позже."); + } + + const activeEnemyIds = activeBattles.map((battle) => battle.enemy); + const enemies = await Enemy.findAll({ where: { id: activeEnemyIds } }); + + if (enemies.length === 0) { + return ctx.reply("В этой локации сейчас нет врагов."); + } + + 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}`, + }; + }); + + const keyboard = Markup.inlineKeyboard( + buttons.map((btn) => [btn]) + ); + + await ctx.reply( + `🔍 Локация: ${location.name}\n\nВыберите врага для охоты:`, + keyboard + ); +}); + +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') { + return ctx.reply("Этот враг уже сражается с другим игроком или сражение завершено."); + } + + const existingBattle = await Battle.findOne({ + where: { character: ctx.from.id, status: 'active' }, + }); + + if (existingBattle) { + return ctx.reply("Вы уже участвуете в другом сражении!"); + } + + const character = await CharacterModel.findOne({ where: { telegram_id: ctx.from.id } }); + if (!character) { + 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_${battle.id}` }]]) + ); +}; + +const enemyTurn = async (ctx, character, battle) => { + const enemy = await Enemy.findByPk(battle.enemy); + + const dodgeChance = Math.min(character.resistance * 0.05, 0.5); + const isDodged = Math.random() < dodgeChance; + + if (isDodged) { + return ctx.reply("💨 Вы уклонились от атаки противника!"); + } + + const damage = enemy.damage; + character.hp -= damage; + + if (character.hp <= 0) { + battle.status = "failed"; + await battle.save(); + return ctx.reply( + `💔 Противник нанес вам ${damage} урона!\n\nВы потерпели поражение от ${enemy.name}.` + ); + } + + await character.save(); + await ctx.reply(`💔 Противник нанес вам ${damage} урона. У вас осталось ${character.hp} HP.`); +}; + +rpg.action(/attack_\d+/, async (ctx) => { + const battleId = ctx.match[0].split("_")[1]; + const battle = await Battle.findByPk(battleId); + + if (!battle || battle.status !== "active") { + return ctx.reply("Сражение завершено или не существует."); + } + + const character = await CharacterModel.findOne({ where: { telegram_id: ctx.from.id } }); + if (!character || battle.character != ctx.from.id) { + return ctx.reply("Это не ваша битва."); + } + + const enemy = await Enemy.findByPk(battle.enemy); + if (!enemy) { + return ctx.reply("Противник не найден."); + } + + // Генерация кнопок + const buttons = []; + const totalButtons = 10; // Всего 10 кнопок + const intelligenceFactor = Math.min(character.intelligence || 1, totalButtons - 1); // Интеллект увеличивает кнопки попадания + const hitDamage = () => Math.floor(Math.random() * (character.force || 10)) + 1; // Урон от силы + const criticalDamage = Math.floor((character.force || 10) * 2); // Критический урон + + // Добавляем кнопки с попаданием + for (let i = 0; i < intelligenceFactor; i++) { + buttons.push({ + text: `Урон: ${hitDamage()}`, + callback_data: `hit_${battleId}`, + }); + } + + // Добавляем кнопку с критическим уроном + buttons.push({ + text: `Крит: ${criticalDamage}`, + callback_data: `critical_${battleId}`, + }); + + // Добавляем кнопки промаха + while (buttons.length < totalButtons) { + buttons.push({ + text: "Промах", + callback_data: `miss_${battleId}`, + }); + } + + // Перетасовываем кнопки и создаём клавиатуру + const shuffledButtons = buttons.sort(() => Math.random() - 0.5); + const keyboard = Markup.inlineKeyboard( + shuffledButtons.map((btn, index) => [btn]).reduce((rows, btn, i) => { + if (i % 2 === 0) rows.push([]); + rows[rows.length - 1].push(btn[0]); + return rows; + }, []) + ); + + // Логи битвы + const logs = battle.logs || []; + const logMessage = logs.slice(-5).map((log) => `• ${log}`).join("\n"); + + // Сообщение с информацией + await ctx.editMessageText( + `⚔️ Сражение с ${enemy.name}\n\n` + + `❤️ Здоровье врага: ${battle.enemy_hp}/${enemy.hp}\n` + + `⚔️ Урон врага: ${enemy.damage}\n\n` + + `📜 Логи битвы:\n${logMessage || "Пока ничего не произошло."}\n\n` + + `🎯 Выберите цель для атаки:`, + keyboard + ); +}); + +// Обработка попадания +rpg.action(/hit_\d+/, async (ctx) => { + const battleId = ctx.match[0].split("_")[1]; + const battle = await Battle.findByPk(battleId); + + if (!battle || battle.status !== "active") { + return ctx.reply("Сражение завершено или не существует."); + } + + const character = await CharacterModel.findOne({ where: { telegram_id: ctx.from.id } }); + if (!character || battle.character != ctx.from.id) { + return ctx.reply("Это не ваша битва."); + } + + const enemy = await Enemy.findByPk(battle.enemy); + if (!enemy) { + return ctx.reply("Противник не найден."); + } + + // Урон + const damage = Math.floor(Math.random() * (character.force || 10)) + 1; + battle.enemy_hp -= damage; + + // Логи + battle.logs = battle.logs || []; + battle.logs.push(`Вы нанесли ${damage} урона врагу.`); + + // Проверка на победу + if (battle.enemy_hp <= 0) { + battle.status = "completed"; + await battle.save(); + return ctx.editMessageText(`🎉 Вы победили ${enemy.name}!`); + } + + await battle.save(); + + // Обновление сообщения с новыми кнопками + const buttons = generateBattleButtons(character, battle); // Генерация новых кнопок + const keyboard = Markup.inlineKeyboard(buttons); + + const logs = battle.logs.slice(-5).map((log) => `• ${log}`).join("\n"); + + await ctx.editMessageText( + `⚔️ Сражение с ${enemy.name}\n\n` + + `❤️ Здоровье врага: ${battle.enemy_hp}/${enemy.hp}\n` + + `⚔️ Урон врага: ${enemy.damage}\n\n` + + `📜 Логи битвы:\n${logs || "Пока ничего не произошло."}\n\n` + + `🎯 Выберите цель для атаки:`, + keyboard + ); +}); + +// Функция для генерации кнопок +function generateBattleButtons(character, battle) { + const buttons = []; + const totalButtons = 10; + const intelligenceFactor = Math.min(character.intelligence || 1, totalButtons - 1); + const hitDamage = () => Math.floor(Math.random() * (character.force || 10)) + 1; + const criticalDamage = Math.floor((character.force || 10) * 2); + + // Генерация кнопок с уроном от атаки + for (let i = 0; i < intelligenceFactor; i++) { + buttons.push({ + text: `Урон: ${hitDamage()}`, + callback_data: `hit_${battle.id}`, + }); + } + + // Генерация кнопки с критическим уроном + buttons.push({ + text: `Крит: ${criticalDamage}`, + callback_data: `critical_${battle.id}`, + }); + + // Заполнение оставшихся кнопок промахами + while (buttons.length < totalButtons) { + buttons.push({ + text: "Промах", + callback_data: `miss_${battle.id}`, + }); + } + + // Перетасовывание кнопок + const shuffledButtons = buttons.sort(() => Math.random() - 0.5); + + // Разбиение кнопок на 2 ряда (5 кнопок в каждой строке) + const rows = []; + for (let i = 0; i < shuffledButtons.length; i += 5) { + rows.push(shuffledButtons.slice(i, i + 5)); + } + + return rows; +} +// Обработка критического удара +rpg.action(/critical_\d+/, async (ctx) => { + const battleId = ctx.match[0].split("_")[1]; + const battle = await Battle.findByPk(battleId); + + if (!battle || battle.status !== "active") { + return ctx.reply("Сражение завершено или не существует."); + } + + const character = await CharacterModel.findOne({ where: { telegram_id: ctx.from.id } }); + if (!character || battle.character != ctx.from.id) { + return ctx.reply("Это не ваша битва."); + } + + const enemy = await Enemy.findByPk(battle.enemy); + if (!enemy) { + return ctx.reply("Противник не найден."); + } + + // Критический урон + const damage = Math.floor((character.force || 10) * 2); + battle.enemy_hp -= damage; + + // Логи + battle.logs = battle.logs || []; + battle.logs.push(`Критический удар! Вы нанесли ${damage} урона врагу.`); + + // Проверка на победу + if (battle.enemy_hp <= 0) { + battle.status = "completed"; + await battle.save(); + return ctx.editMessageText( + `🎉 Вы победили ${enemy.name}!\n\n📜 Логи битвы:\n${battle.logs.slice(-5).map((log) => `• ${log}`).join("\n")}` + ); + } + + await battle.save(); + + // Генерация новых кнопок + const buttons = generateBattleButtons(character, battle); + const keyboard = Markup.inlineKeyboard(buttons); + + const logs = battle.logs.slice(-5).map((log) => `• ${log}`).join("\n"); + + await ctx.editMessageText( + `⚔️ Сражение с ${enemy.name}\n\n` + + `❤️ Здоровье врага: ${battle.enemy_hp}/${enemy.hp}\n` + + `⚔️ Урон врага: ${enemy.damage}\n\n` + + `📜 Логи битвы:\n${logs || "Пока ничего не произошло."}\n\n` + + `🎯 Выберите цель для атаки:`, + keyboard + ); +}); +// Обработка промаха +rpg.action(/miss_\d+/, async (ctx) => { + const battleId = ctx.match[0].split("_")[1]; + const battle = await Battle.findByPk(battleId); + if (!battle || battle.status !== "active") { + return ctx.reply("Сражение завершено или не существует."); + } + const character = await CharacterModel.findOne({ where: { telegram_id: ctx.from.id } }); + if (!character || battle.character != ctx.from.id) { + return ctx.reply("Это не ваша битва."); + } + const enemy = await Enemy.findByPk(battle.enemy); + if (!enemy) { + return ctx.reply("Противник не найден."); + } + + // Логи + battle.logs = battle.logs || []; + battle.logs.push("Вы промахнулись."); + await enemyTurn(ctx, character, battle); + await battle.save(); + + // Генерация новых кнопок + const buttons = generateBattleButtons(character, battle); + const keyboard = Markup.inlineKeyboard(buttons); + + const logs = battle.logs.slice(-5).map((log) => `• ${log}`).join("\n"); + + await ctx.editMessageText( + `⚔️ Сражение с ${enemy.name}\n\n` + + `❤️ Здоровье врага: ${battle.enemy_hp}/${enemy.hp}\n` + + `⚔️ Урон врага: ${enemy.damage}\n\n` + + `📜 Логи битвы:\n${logs || "Пока ничего не произошло."}\n\n` + + `🎯 Выберите цель для атаки:`, + keyboard + ); +});