Files

1253 lines
46 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ========================================
// RPG.JS - Полная RPG система
// ========================================
const RPG = {
// Типы предметов
ItemType: {
WEAPON: 'weapon',
ARMOR: 'armor',
POTION: 'potion',
SCROLL: 'scroll',
QUEST: 'quest',
MATERIAL: 'material',
GOLD: 'gold',
FOOD: 'food',
GEM: 'gem',
KEY: 'key'
},
// Слоты экипировки
EquipmentSlot: {
HEAD: 'head',
CHEST: 'chest',
LEGS: 'legs',
FEET: 'feet',
HANDS: 'hands',
MAIN_HAND: 'main_hand',
OFF_HAND: 'off_hand',
ACCESSORY1: 'accessory1',
ACCESSORY2: 'accessory2'
},
// Типы оружия
WeaponType: {
SWORD: 'sword',
AXE: 'axe',
MACE: 'mace',
DAGGER: 'dagger',
BOW: 'bow',
STAFF: 'staff',
WAND: 'wand'
},
// Типы брони
ArmorType: {
HELMET: 'helmet',
CHESTPLATE: 'chestplate',
LEGGINGS: 'leggings',
BOOTS: 'boots',
SHIELD: 'shield',
CLOAK: 'cloak'
},
// Редкость предметов
Rarity: {
COMMON: 'common',
UNCOMMON: 'uncommon',
RARE: 'rare',
EPIC: 'epic',
LEGENDARY: 'legendary',
MYTHIC: 'mythic'
},
// Цвета редкости
RarityColors: {
common: '#b0b0b0',
uncommon: '#44ff44',
rare: '#4444ff',
epic: '#aa44ff',
legendary: '#ffaa00',
mythic: '#ff44ff'
},
// ========================================
// СОЗДАНИЕ ПЕРСОНАЖА
// ========================================
createCharacter(name, gender = 'male', classType = 'warrior') {
const classStats = {
warrior: {
hp: 120, mp: 30, str: 12, def: 10, mag: 5, spd: 8,
description: 'Сильный воин с высокой защитой'
},
mage: {
hp: 70, mp: 100, str: 4, def: 4, mag: 15, spd: 6,
description: 'Мастер магии с мощными заклинаниями'
},
archer: {
hp: 90, mp: 50, str: 10, def: 6, mag: 6, spd: 12,
description: 'Быстрый стрелок с высокой ловкостью'
},
thief: {
hp: 85, mp: 45, str: 8, def: 5, mag: 8, spd: 14,
description: 'Ловкий плут, мастер скрытности'
},
paladin: {
hp: 110, mp: 60, str: 10, def: 12, mag: 8, spd: 6,
description: 'Святой воин с лечащей магией'
},
necromancer: {
hp: 80, mp: 90, str: 6, def: 6, mag: 14, spd: 7,
description: 'Мастер темной магии и призыва'
}
};
const stats = classStats[classType] || classStats.warrior;
return {
id: 'player_' + Date.now(),
name: name,
gender: gender,
class: classType,
description: classStats[classType].description,
level: 1,
exp: 0,
expToNextLevel: 100,
hp: stats.hp,
maxHp: stats.hp,
mp: stats.mp,
maxMp: stats.mp,
baseStr: stats.str,
baseDef: stats.def,
baseMag: stats.mag,
baseSpd: stats.spd,
str: stats.str,
def: stats.def,
mag: stats.mag,
spd: stats.spd,
gold: 50,
hairColor: '#4a3520',
// Позиция
x: 5,
y: 5,
targetX: 5,
targetY: 5,
isMoving: false,
moveProgress: 0,
// Инвентарь и экипировка
inventory: [],
equipment: {
head: null,
chest: null,
legs: null,
feet: null,
main_hand: null,
off_hand: null,
accessory1: null,
accessory2: null
},
// Навыки и заклинания
skills: [],
spells: [],
learnedSpells: [],
// Квесты
quests: [],
completedQuests: [],
// Достижения
achievements: [],
// Боевые флаги
inCombat: false,
currentEnemy: null,
combatStats: {
attacks: 0,
damageDealt: 0,
damageTaken: 0,
enemiesKilled: 0
}
};
},
// ========================================
// СОЗДАНИЕ ПРЕДМЕТОВ
// ========================================
createItem(id, type, name, options = {}) {
return {
id: id || 'item_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9),
type: type,
name: name,
description: options.description || '',
value: options.value || 0,
rarity: options.rarity || 'common',
// Для оружия
damage: options.damage || 0,
damageType: options.damageType || 'physical', // physical, magic, fire, ice, lightning
weaponType: options.weaponType || null,
// Для брони
defense: options.defense || 0,
armorType: options.armorType || null,
// Для зелий
healAmount: options.healAmount || 0,
restoreMp: options.restoreMp || 0,
// Для еды
hungerRestore: options.hungerRestore || 0,
// Для свитков/книг
spell: options.spell || null,
spellLevel: options.spellLevel || 1,
// Требования
requiredLevel: options.requiredLevel || 1,
requiredClass: options.requiredClass || null,
// Особые свойства
special: options.special || null, // { name: 'something', value: x }
// Визуал
icon: options.icon || '?',
color: options.color || null,
// Остальное
stackable: options.stackable || false,
quantity: options.quantity || 1,
subtype: options.subtype || type,
// Статы
bonusStr: options.bonusStr || 0,
bonusDef: options.bonusDef || 0,
bonusMag: options.bonusMag || 0,
bonusSpd: options.bonusSpd || 0,
bonusHp: options.bonusHp || 0,
bonusMp: options.bonusMp || 0
};
},
// ========================================
// СОЗДАНИЕ ВРАГОВ
// ========================================
createEnemy(type, level, x, y) {
const enemyTemplates = {
// Обычные враги
goblin: {
name: 'Гоблин',
baseHp: 30, baseDmg: 8, baseDef: 2,
exp: 20, gold: 10,
lootTable: ['goblin_ear', 'herb'],
behavior: 'aggressive'
},
orc: {
name: 'Орк',
baseHp: 50, baseDmg: 12, baseDef: 4,
exp: 40, gold: 25,
lootTable: ['orc_tusk', 'meat'],
behavior: 'aggressive'
},
skeleton: {
name: 'Скелет',
baseHp: 40, baseDmg: 10, baseDef: 6,
exp: 30, gold: 15,
lootTable: ['bone', 'bone_dagger'],
behavior: 'aggressive'
},
slime: {
name: 'Слизень',
baseHp: 20, baseDmg: 5, baseDef: 0,
exp: 10, gold: 5,
lootTable: ['slime_gel'],
behavior: 'passive'
},
bandit: {
name: 'Разбойник',
baseHp: 35, baseDmg: 9, baseDef: 3,
exp: 25, gold: 20,
lootTable: ['money_pouch', 'dagger'],
behavior: 'aggressive'
},
// Маги
mage: {
name: 'Маг',
baseHp: 25, baseDmg: 20, baseDef: 2, baseMp: 50,
exp: 50, gold: 30,
lootTable: ['mana_potion', 'scroll'],
behavior: 'ranged'
},
// Элитные
troll: {
name: 'Тролль',
baseHp: 100, baseDmg: 18, baseDef: 8,
exp: 80, gold: 60,
lootTable: ['troll_heart', 'club'],
behavior: 'aggressive'
},
// Боссы
dragon: {
name: 'Дракон',
baseHp: 200, baseDmg: 30, baseDef: 15,
exp: 200, gold: 200,
lootTable: ['dragon_scale', 'dragon_heart', 'dragon_egg'],
behavior: 'boss',
isBoss: true
},
demon: {
name: 'Демон',
baseHp: 150, baseDmg: 25, baseDef: 10,
exp: 150, gold: 150,
lootTable: ['demon_heart', 'infernal_orb'],
behavior: 'boss',
isBoss: true
},
lich: {
name: 'Лич',
baseHp: 100, baseDmg: 35, baseDef: 8, baseMp: 100,
exp: 180, gold: 120,
lootTable: ['necronomicon', 'skull_staff'],
behavior: 'boss',
isBoss: true
}
};
const template = enemyTemplates[type] || enemyTemplates.goblin;
const levelMultiplier = 1 + (level - 1) * 0.2;
return {
id: 'enemy_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9),
type: type,
name: template.name,
level: level,
hp: Math.floor(template.baseHp * levelMultiplier),
maxHp: Math.floor(template.baseHp * levelMultiplier),
mp: (template.baseMp || 0) * levelMultiplier,
maxMp: (template.baseMp || 0) * levelMultiplier,
damage: Math.floor(template.baseDmg * levelMultiplier),
defense: Math.floor((template.baseDef || 0) * levelMultiplier),
exp: Math.floor(template.exp * levelMultiplier),
gold: Math.floor(template.gold * levelMultiplier),
lootTable: template.lootTable,
behavior: template.behavior,
isBoss: template.isBoss || false,
x: x,
y: y,
height: type === 'slime' ? 25 : 45,
isAttacking: false,
attackCooldown: 0
};
},
// ========================================
// СОЗДАНИЕ NPC
// ========================================
createNPC(name, x, y, options = {}) {
return {
id: 'npc_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9),
name: name,
x: x,
y: y,
color: options.color || '#8b6914',
// Диалоги
dialog: options.dialog || 'Приветствую, путник!',
dialogOptions: options.dialogOptions || [],
// Торговля
shop: options.shop || null,
shopName: options.shopName || 'Магазин',
// Квесты
quests: options.quests || [],
// Услуги
services: options.services || [], // heal, teleport, identify
// Внешность
sprite: options.sprite || 'villager',
// Отношения
faction: options.faction || 'neutral',
attitude: options.attitude || 0
};
},
// ========================================
// КВЕСТОВАЯ СИСТЕМА
// ========================================
QuestStatus: {
NOT_STARTED: 'not_started',
ACTIVE: 'active',
COMPLETED: 'completed',
FAILED: 'failed'
},
createQuest(id, name, description, options = {}) {
return {
id: id,
name: name,
description: description,
// Тип квеста
type: options.type || 'kill', // kill, collect, talk, explore, deliver
// Цели
objectives: options.objectives || [],
// Награды
rewards: options.rewards || { exp: 0, gold: 0, items: [] },
// Требования
requiredLevel: options.requiredLevel || 1,
requiredQuest: options.requiredQuest || null,
// Класс
requiredClass: options.requiredClass || null,
// Цепочка
nextQuest: options.nextQuest || null,
// Отслеживание
progress: {},
status: 'not_started'
};
},
// Добавить квест персонажу
addQuest(character, quest) {
if (character.completedQuests.includes(quest.id)) return false;
if (character.quests.find(q => q.id === quest.id)) return false;
const newQuest = JSON.parse(JSON.stringify(quest));
newQuest.status = 'active';
// Инициализация прогресса
newQuest.objectives.forEach(obj => {
newQuest.progress[obj.id] = { current: 0, target: obj.target, completed: false };
});
character.quests.push(newQuest);
return true;
},
// Обновить прогресс квеста
updateQuestProgress(character, objectiveType, targetId, amount = 1) {
character.quests.forEach(quest => {
if (quest.status !== 'active') return;
quest.objectives.forEach(obj => {
if (obj.type === objectiveType && obj.target === targetId) {
quest.progress[obj.id].current += amount;
if (quest.progress[obj.id].current >= obj.target) {
quest.progress[obj.id].completed = true;
}
}
});
});
},
// Проверить завершение квеста
checkQuestCompletion(character, questId) {
const quest = character.quests.find(q => q.id === questId);
if (!quest || quest.status !== 'active') return false;
const allCompleted = quest.objectives.every(obj => quest.progress[obj.id].completed);
if (allCompleted) {
quest.status = 'completed';
return true;
}
return false;
},
// Получить награду квеста
completeQuest(character, questId) {
const questIndex = character.quests.findIndex(q => q.id === questId);
if (questIndex === -1) return null;
const quest = character.quests[questIndex];
if (quest.status !== 'completed') return null;
// Удаляем из активных и добавляем в завершённые
character.quests.splice(questIndex, 1);
character.completedQuests.push(questId);
// Выдаём награды
character.exp += quest.rewards.exp;
character.gold += quest.rewards.gold;
const items = [];
if (quest.rewards.items) {
quest.rewards.items.forEach(itemTemplate => {
const item = this.createItem(itemTemplate.id, itemTemplate.type, itemTemplate.name, itemTemplate);
this.addItemToInventory(character, item);
items.push(item);
});
}
return { quest, items };
},
// ========================================
// МАГИЧЕСКАЯ СИСТЕМА
// ========================================
SpellType: {
FIRE: 'fire',
ICE: 'ice',
LIGHTNING: 'lightning',
HEALING: 'healing',
BUFF: 'buff',
DEBUFF: 'debuff',
SUMMON: 'summon',
DARK: 'dark',
HOLY: 'holy'
},
SpellSchool: {
ELEMENTAL: 'elemental',
MYSTIC: 'mystic',
DARK: 'dark',
HOLY: 'holy'
},
// База заклинаний
SpellDatabase: {
// Огненная магия
fireball: {
name: 'Огненный шар',
type: 'fire',
school: 'elemental',
mpCost: 15,
damage: 25,
damageType: 'fire',
range: 5,
cooldown: 3000,
description: 'Наносит урон огнём'
},
fireblast: {
name: 'Огненный взрыв',
type: 'fire',
school: 'elemental',
mpCost: 25,
damage: 40,
damageType: 'fire',
range: 3,
cooldown: 5000,
description: 'Мощный взрыв огня вокруг'
},
// Ледяная магия
frostbolt: {
name: 'Ледяная стрела',
type: 'ice',
school: 'elemental',
mpCost: 12,
damage: 20,
damageType: 'ice',
range: 6,
cooldown: 2000,
description: 'Наносит урон льдом и замедляет'
},
blizzard: {
name: 'Метель',
type: 'ice',
school: 'elemental',
mpCost: 35,
damage: 50,
damageType: 'ice',
range: 4,
cooldown: 8000,
description: 'Атакует всех врагов вокруг'
},
// Молния
lightning: {
name: 'Молния',
type: 'lightning',
school: 'elemental',
mpCost: 20,
damage: 35,
damageType: 'lightning',
range: 6,
cooldown: 3000,
description: 'Быстрая атака молнией'
},
// Лечение
heal: {
name: 'Исцеление',
type: 'healing',
school: 'mystic',
mpCost: 10,
heal: 30,
range: 4,
cooldown: 4000,
description: 'Восстанавливает здоровье'
},
greater_heal: {
name: 'Greater Heal',
type: 'healing',
school: 'mystic',
mpCost: 25,
heal: 70,
range: 4,
cooldown: 6000,
description: 'Мощное исцеление'
},
// Баффы
power: {
name: 'Усиление',
type: 'buff',
school: 'mystic',
mpCost: 15,
buff: { stat: 'damage', value: 1.5, duration: 10000 },
cooldown: 15000,
description: 'Увеличивает урон на 50%'
},
shield: {
name: 'Щит',
type: 'buff',
school: 'mystic',
mpCost: 20,
buff: { stat: 'defense', value: 2, duration: 15000 },
cooldown: 20000,
description: 'Удваивает защиту'
},
// Тёмная магия
life_drain: {
name: 'Похищение жизни',
type: 'dark',
school: 'dark',
mpCost: 18,
damage: 15,
heal: 10,
range: 4,
cooldown: 5000,
description: 'Наносит урон и восстанавливает HP'
},
curse: {
name: 'Проклятие',
type: 'debuff',
school: 'dark',
mpCost: 15,
debuff: { stat: 'damage', value: 0.7, duration: 8000 },
range: 5,
cooldown: 10000,
description: 'Снижает урон врага на 30%'
},
// Святая магия
holy_fire: {
name: 'Священный огонь',
type: 'holy',
school: 'holy',
mpCost: 22,
damage: 30,
damageType: 'holy',
range: 5,
cooldown: 4000,
description: 'Атакует нечисть'
},
resurrect: {
name: 'Воскрешение',
type: 'holy',
school: 'holy',
mpCost: 50,
resurrect: true,
cooldown: 60000,
description: 'Воскрешает союзника в бою'
}
},
// Изучить заклинание
learnSpell(character, spellId) {
const spell = this.SpellDatabase[spellId];
if (!spell) return false;
if (character.learnedSpells.includes(spellId)) return false;
// Проверка требований (уровень, класс)
if (spell.requiredLevel && character.level < spell.requiredLevel) return false;
if (spell.requiredClass && character.class !== spell.requiredClass) return false;
character.learnedSpells.push(spellId);
return true;
},
// Использовать заклинание
castSpell(character, spellId, target = null) {
const spell = this.SpellDatabase[spellId];
if (!spell) return { success: false, message: 'Заклинание не найдено!' };
if (!character.learnedSpells.includes(spellId)) return { success: false, message: 'Вы не изучили это заклинание!' };
// Проверка MP
if (character.mp < spell.mpCost) return { success: false, message: 'Недостаточно маны!' };
// Проверка кулдауна
const lastCast = character.combatStats.lastSpellCast || {};
const now = Date.now();
if (lastCast[spellId] && now - lastCast[spellId] < spell.cooldown) {
return { success: false, message: 'Заклинание на перезарядке!' };
}
// Списываем ману
character.mp -= spell.mpCost;
// Записываем кулдаун
character.combatStats.lastSpellCast = character.combatStats.lastSpellCast || {};
character.combatStats.lastSpellCast[spellId] = now;
return {
success: true,
spell: spell,
target: target,
message: `Прочитано: ${spell.name}!`
};
},
// ========================================
// ИНВЕНТАРЬ И ЭКИПИРОВКА
// ========================================
addItemToInventory(character, item) {
// Проверка стекируемости
if (item.stackable) {
const existing = character.inventory.find(i =>
i.id === item.id && i.stackable
);
if (existing) {
existing.quantity += item.quantity;
return true;
}
}
// Проверка лимита инвентаря
if (character.inventory.length >= 25) {
return false;
}
character.inventory.push(item);
return true;
},
removeItemFromInventory(character, itemId, quantity = 1) {
const index = character.inventory.findIndex(i => i.id === itemId);
if (index === -1) return false;
const item = character.inventory[index];
if (item.stackable) {
if (item.quantity >= quantity) {
item.quantity -= quantity;
if (item.quantity <= 0) {
character.inventory.splice(index, 1);
}
return true;
}
} else {
character.inventory.splice(index, 1);
return true;
}
return false;
},
equipItem(character, item) {
if (item.type !== this.ItemType.WEAPON && item.type !== this.ItemType.ARMOR) {
return { success: false, message: 'Этот предмет нельзя экипировать!' };
}
// Проверка требований
if (item.requiredLevel && character.level < item.requiredLevel) {
return { success: false, message: `Требуется уровень ${item.requiredLevel}!` };
}
if (item.requiredClass && character.class !== item.requiredClass) {
return { success: false, message: `Только для класса ${item.requiredClass}!` };
}
let slot;
if (item.type === this.ItemType.WEAPON) {
slot = this.EquipmentSlot.MAIN_HAND;
} else if (item.type === this.ItemType.ARMOR) {
switch(item.armorType) {
case this.ArmorType.HELMET: slot = this.EquipmentSlot.HEAD; break;
case this.ArmorType.CHESTPLATE: slot = this.EquipmentSlot.CHEST; break;
case this.ArmorType.LEGGINGS: slot = this.EquipmentSlot.LEGS; break;
case this.ArmorType.BOOTS: slot = this.EquipmentSlot.FEET; break;
case this.ArmorType.SHIELD: slot = this.EquipmentSlot.OFF_HAND; break;
case this.ArmorType.CLOAK: slot = this.EquipmentSlot.CHEST; break;
default: slot = this.EquipmentSlot.CHEST;
}
}
// Снятие текущего предмета
if (character.equipment[slot]) {
this.unequipItem(character, slot);
}
// Экипировка
character.equipment[slot] = item;
// Удаляем из инвентаря
const invIndex = character.inventory.findIndex(i => i.id === item.id);
if (invIndex >= 0) {
character.inventory.splice(invIndex, 1);
}
return { success: true, message: `Экипировано: ${item.name}` };
},
unequipItem(character, slot) {
const item = character.equipment[slot];
if (!item) return false;
this.addItemToInventory(character, item);
character.equipment[slot] = null;
return true;
},
// Получение бонусов от экипировки
getEquipmentBonuses(character) {
const bonuses = {
damage: 0,
defense: 0,
hp: 0,
mp: 0,
str: 0,
def: 0,
mag: 0,
spd: 0
};
Object.values(character.equipment).forEach(item => {
if (!item) return;
bonuses.damage += item.damage || 0;
bonuses.defense += item.defense || 0;
bonuses.hp += item.bonusHp || 0;
bonuses.mp += item.bonusMp || 0;
bonuses.str += item.bonusStr || 0;
bonuses.def += item.bonusDef || 0;
bonuses.mag += item.bonusMag || 0;
bonuses.spd += item.bonusSpd || 0;
});
return bonuses;
},
// Полный расчет статов
getTotalStats(character) {
const bonuses = this.getEquipmentBonuses(character);
return {
hp: (character.maxHp || 100) + bonuses.hp,
mp: (character.maxMp || 50) + bonuses.mp,
damage: (character.str || 10) + bonuses.damage,
defense: (character.def || 5) + bonuses.defense,
magic: (character.mag || 5) + bonuses.mag,
speed: (character.spd || 5) + bonuses.spd
};
},
// Использование предмета
useItem(character, item) {
if (item.type === this.ItemType.POTION) {
const oldHp = character.hp;
const oldMp = character.mp;
character.hp = Math.min(character.hp + (item.healAmount || 0), character.maxHp);
character.mp = Math.min(character.mp + (item.restoreMp || 0), character.maxMp);
const healed = character.hp - oldHp;
const restored = character.mp - oldMp;
if (item.stackable) {
this.removeItemFromInventory(character, item.id, 1);
} else {
this.removeItemFromInventory(character, item.id);
}
return {
success: true,
message: healed > 0 ? `HP: +${healed}` : `MP: +${restored}`,
used: true
};
}
if (item.type === this.ItemType.FOOD) {
character.hp = Math.min(character.hp + (item.healAmount || 20), character.maxHp);
if (item.stackable) {
this.removeItemFromInventory(character, item.id, 1);
} else {
this.removeItemFromInventory(character, item.id);
}
return { success: true, message: `Съедено: ${item.name}` };
}
if (item.type === this.ItemType.SCROLL && item.spell) {
return {
success: true,
message: `Вы изучили заклинание: ${item.spell}!`,
scrollUsed: true,
spellLearned: item.spell
};
}
return { success: false, message: 'Этот предмет нельзя использовать!' };
},
// ========================================
// БОЕВАЯ СИСТЕМА
// ========================================
attack(attacker, defender) {
const attackerStats = attacker.isPlayer ? this.getTotalStats(attacker) : {
damage: attacker.damage,
defense: attacker.defense || 0,
speed: attacker.level * 2
};
const defenderStats = defender.isPlayer ? this.getTotalStats(defender) : {
damage: defender.damage || 0,
defense: defender.defense || 0,
speed: defender.level * 2
};
// Базовый урон
let damage = Math.max(1, attackerStats.damage - defenderStats.defense);
// Критический удар (10% базовый + бонус от скорости)
const critChance = 0.1 + (attackerStats.speed || 0) * 0.01;
const isCrit = Math.random() < critChance;
if (isCrit) {
damage *= 1.5 + (Math.random() * 0.5);
}
// Элементальная уязвимость
if (attacker.damageType && defender.weakness === attacker.damageType) {
damage *= 1.5;
}
// Случайное отклонение (±15%)
const variance = 0.85 + Math.random() * 0.3;
damage = Math.floor(damage * variance);
defender.hp = Math.max(0, defender.hp - damage);
// Статистика
if (attacker.isPlayer) {
attacker.combatStats.damageDealt += damage;
attacker.combatStats.attacks++;
}
if (defender.isPlayer) {
defender.combatStats.damageTaken += damage;
}
return {
damage: damage,
isCrit: isCrit,
killed: defender.hp <= 0
};
},
// Атака врага (AI)
enemyAttack(enemy, player) {
const stats = RPG.getTotalStats(player);
let damage = Math.max(1, enemy.damage - stats.defense);
const isCrit = Math.random() < 0.1;
if (isCrit) damage *= 1.5;
damage = Math.floor(damage * (0.9 + Math.random() * 0.2));
player.hp = Math.max(0, player.hp - damage);
player.combatStats.damageTaken += damage;
return { damage, isCrit, killed: player.hp <= 0 };
},
// ========================================
// СИСТЕМА УРОВНЕЙ
// ========================================
checkLevelUp(character) {
let leveledUp = false;
while (character.exp >= character.expToNextLevel) {
character.exp -= character.expToNextLevel;
character.level++;
character.expToNextLevel = Math.floor(100 * Math.pow(1.5, character.level - 1));
// Бонус к статам по классу
const classBonuses = {
warrior: { hp: 15, mp: 3, str: 3, def: 2, mag: 1, spd: 1 },
mage: { hp: 5, mp: 15, str: 1, def: 1, mag: 3, spd: 1 },
archer: { hp: 8, mp: 5, str: 2, def: 1, mag: 1, spd: 2 },
thief: { hp: 7, mp: 4, str: 2, def: 1, mag: 2, spd: 3 },
paladin: { hp: 12, mp: 8, str: 2, def: 3, mag: 2, spd: 1 },
necromancer: { hp: 6, mp: 12, str: 1, def: 1, mag: 4, spd: 1 }
};
const bonus = classBonuses[character.class] || classBonuses.warrior;
character.maxHp += bonus.hp;
character.maxMp += bonus.mp;
character.baseStr += bonus.str;
character.baseDef += bonus.def;
character.baseMag += bonus.mag;
character.baseSpd += bonus.spd;
// Пересчитываем текущие статы
character.str = character.baseStr;
character.def = character.baseDef;
character.mag = character.baseMag;
character.spd = character.baseSpd;
// Восстановление при левелапе
character.hp = character.maxHp;
character.mp = character.maxMp;
leveledUp = true;
}
return leveledUp;
},
// ========================================
// ГЕНЕРАЦИЯ ЛУТА
// ========================================
generateLoot(enemy) {
const loot = [];
// Всегда даёт золото
loot.push(this.createItem(
'gold',
this.ItemType.GOLD,
'Золото',
{ value: enemy.gold, stackable: true, quantity: enemy.gold, rarity: 'common' }
));
// Шанс выпадения из таблицы
if (enemy.lootTable) {
enemy.lootTable.forEach(lootId => {
if (Math.random() < 0.2) { // 20% шанс на каждый предмет
loot.push(this.createLootItem(lootId, enemy.level));
}
});
}
// Шанс случайного предмета (30%)
if (Math.random() < 0.3) {
const randomItems = [
{ type: 'potion', healAmount: 30 },
{ type: 'potion', healAmount: 50 },
{ type: 'potion', restoreMp: 20 },
{ type: 'food', healAmount: 15 },
{ type: 'gold', value: Math.floor(enemy.gold * 0.5) }
];
const template = randomItems[Math.floor(Math.random() * randomItems.length)];
loot.push(this.createItem(
'loot_' + Date.now(),
template.type,
template.type === 'gold' ? 'Золото' : 'Зелье',
template
));
}
return loot;
},
createLootItem(lootId, level) {
const lootDatabase = {
// Материалы
goblin_ear: { name: 'Ухо гоблина', value: 5, type: 'material' },
orc_tusk: { name: 'Клык орка', value: 10, type: 'material' },
bone: { name: 'Кость', value: 3, type: 'material' },
herb: { name: 'Трава', value: 8, type: 'material' },
slime_gel: { name: 'Слизь', value: 5, type: 'material' },
troll_heart: { name: 'Сердце тролля', value: 50, type: 'material', rarity: 'rare' },
dragon_scale: { name: 'Чешуя дракона', value: 100, type: 'material', rarity: 'epic' },
demon_heart: { name: 'Сердце демона', value: 80, type: 'material', rarity: 'epic' },
// Оружие
bone_dagger: { name: 'Костяной кинжал', value: 25, type: 'weapon', damage: 8, rarity: 'uncommon' },
club: { name: 'Дубина', value: 15, type: 'weapon', damage: 6 },
skull_staff: { name: 'Посох черепа', value: 80, type: 'weapon', damage: 15, rarity: 'rare' },
// Щиты
shield_wood: { name: 'Деревянный щит', value: 20, type: 'armor', defense: 3 },
// Зелья
health_potion: { name: 'Зелье HP', value: 25, type: 'potion', healAmount: 40, rarity: 'uncommon' },
mana_potion: { name: 'Зелье MP', value: 30, type: 'potion', restoreMp: 30, rarity: 'uncommon' },
// Еда
meat: { name: 'Мясо', value: 10, type: 'food', healAmount: 20 },
// Книги
scroll: { name: 'Свиток', value: 50, type: 'scroll', spell: 'fireball' },
necronomicon: { name: 'Некрономикон', value: 200, type: 'scroll', spell: 'life_drain', rarity: 'rare' },
// Разное
money_pouch: { name: 'Кошелёк', value: 15, type: 'gold' },
dragon_heart: { name: 'Сердце дракона', value: 300, type: 'material', rarity: 'legendary' },
dragon_egg: { name: 'Яйцо дракона', value: 500, type: 'quest', rarity: 'legendary' },
infernal_orb: { name: 'Инфернальная сфера', value: 150, type: 'material', rarity: 'epic' }
};
const template = lootDatabase[lootId] || { name: 'Предмет', value: 10, type: 'material' };
return this.createItem(
lootId + '_' + Date.now(),
template.type,
template.name,
{
value: template.value,
damage: template.damage,
defense: template.defense,
healAmount: template.healAmount,
restoreMp: template.restoreMp,
spell: template.spell,
rarity: template.rarity || 'common'
}
);
},
// ========================================
// ПРОВЕРКА ПРОХОДИМОСТИ
// ========================================
isTilePassable(map, x, y) {
if (x < 0 || y < 0 || x >= map[0].length || y >= map.length) {
return false;
}
const tile = map[y][x];
return tile !== 1 && tile !== 4 && tile !== 6; // вода, стена, лава
},
getAdjacentTiles(x, y, map) {
const directions = [
{ dx: 0, dy: -1 },
{ dx: 1, dy: 0 },
{ dx: 0, dy: 1 },
{ dx: -1, dy: 0 }
];
return directions
.map(d => ({ x: x + d.dx, y: y + d.dy }))
.filter(pos => this.isTilePassable(map, pos.x, pos.y));
},
// ========================================
// КОЛЛИЗИИ
// ========================================
checkEnemyCollision(character, enemies) {
const charX = Math.round(character.x);
const charY = Math.round(character.y);
for (let enemy of enemies) {
const enemyX = Math.round(enemy.x);
const enemyY = Math.round(enemy.y);
if (charX === enemyX && charY === enemyY) {
return enemy;
}
}
return null;
},
checkItemCollision(character, items) {
const charX = Math.round(character.x);
const charY = Math.round(character.y);
for (let i = 0; i < items.length; i++) {
const item = items[i];
if (!item.collected && item.x === charX && item.y === charY) {
return i;
}
}
return -1;
},
checkNPCCollision(character, npcs) {
const charX = Math.round(character.x);
const charY = Math.round(character.y);
for (let npc of npcs) {
if (npc.x === charX && npc.y === charY) {
return npc;
}
}
return null;
},
// ========================================
// СОХРАНЕНИЕ И ЗАГРУЗКА
// ========================================
saveGame(character, filename = 'rpg_save') {
const saveData = {
version: '1.0',
timestamp: Date.now(),
character: character
};
try {
localStorage.setItem(filename, JSON.stringify(saveData));
return { success: true, message: 'Игра сохранена!' };
} catch (e) {
return { success: false, message: 'Ошибка сохранения!' };
}
},
loadGame(filename = 'rpg_save') {
try {
const saveData = localStorage.getItem(filename);
if (!saveData) {
return { success: false, message: 'Нет сохранённой игры!' };
}
const data = JSON.parse(saveData);
return { success: true, character: data.character, message: 'Игра загружена!' };
} catch (e) {
return { success: false, message: 'Ошибка загрузки!' };
}
},
deleteSave(filename = 'rpg_save') {
try {
localStorage.removeItem(filename);
return { success: true, message: 'Сохранение удалено!' };
} catch (e) {
return { success: false, message: 'Ошибка удаления!' };
}
},
hasSave(filename = 'rpg_save') {
return localStorage.getItem(filename) !== null;
},
// ========================================
// БАЗЫ ДАННЫХ
// ========================================
ItemDatabase: {
weapons: [
{ id: 'sword_1', name: 'Железный меч', damage: 5, value: 50 },
{ id: 'sword_2', name: 'Стальной меч', damage: 10, value: 150 },
{ id: 'sword_3', name: 'Меч рыцаря', damage: 18, value: 400 },
{ id: 'sword_legend', name: 'Экскалибур', damage: 35, value: 2000, rarity: 'legendary' },
{ id: 'axe_1', name: 'Боевой топор', damage: 8, value: 80 },
{ id: 'axe_2', name: 'Секира варвара', damage: 15, value: 300 },
{ id: 'staff_1', name: 'Посох ученика', damage: 3, magic: 8, value: 60 },
{ id: 'staff_2', name: 'Посох мага', damage: 5, magic: 15, value: 200 },
{ id: 'dagger_1', name: 'Кинжал', damage: 4, value: 30 },
{ id: 'bow_1', name: 'Лук', damage: 7, value: 70 }
],
armor: [
{ id: 'helmet_1', name: 'Кожаный шлем', defense: 2, value: 40 },
{ id: 'helmet_2', name: 'Железный шлем', defense: 5, value: 120 },
{ id: 'chest_1', name: 'Кожаная куртка', defense: 3, value: 50 },
{ id: 'chest_2', name: 'Кольчуга', defense: 8, value: 200 },
{ id: 'chest_3', name: 'Латы рыцаря', defense: 15, value: 500 },
{ id: 'chest_epic', name: 'Доспех дракона', defense: 25, value: 1500, rarity: 'epic' },
{ id: 'legs_1', name: 'Кожаные поножи', defense: 2, value: 35 },
{ id: 'boots_1', name: 'Кожаные сапоги', defense: 1, value: 25 },
{ id: 'boots_2', name: 'Железные сапоги', defense: 3, value: 80 },
{ id: 'shield_1', name: 'Деревянный щит', defense: 3, value: 40 },
{ id: 'shield_2', name: 'Железный щит', defense: 6, value: 150 },
{ id: 'shield_epic', name: 'Щит небожителя', defense: 12, value: 800, rarity: 'epic' }
],
potions: [
{ id: 'health_potion_1', name: 'Малое зелье HP', healAmount: 30, value: 20 },
{ id: 'health_potion_2', name: 'Среднее зелье HP', healAmount: 60, value: 50 },
{ id: 'health_potion_3', name: 'Большое зелье HP', healAmount: 100, value: 100 },
{ id: 'mana_potion_1', name: 'Малое зелье MP', restoreMp: 20, value: 25 },
{ id: 'mana_potion_2', name: 'Среднее зелье MP', restoreMp: 40, value: 60 },
{ id: 'mana_potion_3', name: 'Большое зелье MP', restoreMp: 70, value: 120 },
{ id: 'elixir_1', name: 'Эликсир жизни', healAmount: 200, value: 300, rarity: 'rare' }
]
}
};
// Экспорт
if (typeof module !== 'undefined' && module.exports) {
module.exports = RPG;
}