Battle Update Prepare
This commit is contained in:
Degradin 2025-01-12 03:37:21 +03:00
parent fb757f9f0a
commit b4087cf26c
5 changed files with 584 additions and 0 deletions

View File

@ -19,6 +19,9 @@ module.exports = {
CharacterModel : require('../models/character.model'), CharacterModel : require('../models/character.model'),
InventoryModel : require('../models/inventory.model'), InventoryModel : require('../models/inventory.model'),
ItemsModel : require('../models/items.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'), StolenCardsModel : require('../models/stolencards.model'),
WorldModel : require('../models/world.model'), WorldModel : require('../models/world.model'),
JobModel : require('../models/job.model'), JobModel : require('../models/job.model'),

33
models/battle.model.js Normal file
View File

@ -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;

40
models/enemy.model.js Normal file
View File

@ -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;

38
models/location.model.js Normal file
View File

@ -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;

470
rpg.js
View File

@ -14,6 +14,9 @@ const {
CharacterModel, CharacterModel,
InventoryModel, InventoryModel,
ItemsModel, ItemsModel,
Enemy,
Location,
Battle,
StolenCardsModel, StolenCardsModel,
WorldModel, WorldModel,
PropertyModel, 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
);
});