import { MigrationInterface, QueryRunner } from 'typeorm'; export class TurnCombatSystem1743004800000 implements MigrationInterface { name = 'TurnCombatSystem1743004800000'; public async up(queryRunner: QueryRunner): Promise { // --- Mana sur characters --- await queryRunner.query(` ALTER TABLE \`characters\` ADD COLUMN \`mana_current\` INT NOT NULL DEFAULT 50 AFTER \`hp_max\`, ADD COLUMN \`mana_max\` INT NOT NULL DEFAULT 50 AFTER \`mana_current\` `); // --- AI profile sur monsters --- await queryRunner.query(` ALTER TABLE \`monsters\` ADD COLUMN \`ai_profile\` VARCHAR(20) NOT NULL DEFAULT 'aggressive' AFTER \`zone\`, ADD COLUMN \`is_boss\` TINYINT(1) NOT NULL DEFAULT 0 AFTER \`ai_profile\` `); // --- Table des sorts --- await queryRunner.query(` CREATE TABLE \`spells\` ( \`id\` VARCHAR(36) NOT NULL, \`name\` VARCHAR(100) NOT NULL, \`path\` VARCHAR(20) NOT NULL, \`path_level\` INT NOT NULL, \`mana_cost\` INT NOT NULL, \`cooldown\` INT NOT NULL, \`target_type\` VARCHAR(20) NOT NULL, \`description\` TEXT NOT NULL, \`effects\` JSON NOT NULL, \`unlock_cost\` INT NOT NULL DEFAULT 0, PRIMARY KEY (\`id\`) ) ENGINE=InnoDB `); // --- Sorts debloques par joueur --- await queryRunner.query(` CREATE TABLE \`player_spells\` ( \`id\` VARCHAR(36) NOT NULL, \`character_id\` VARCHAR(36) NOT NULL, \`spell_id\` VARCHAR(36) NOT NULL, \`unlocked_at\` DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), PRIMARY KEY (\`id\`), UNIQUE INDEX \`IDX_player_spells_char_spell\` (\`character_id\`, \`spell_id\`), INDEX \`IDX_player_spells_character\` (\`character_id\`), CONSTRAINT \`FK_player_spells_character\` FOREIGN KEY (\`character_id\`) REFERENCES \`characters\`(\`id\`) ON DELETE CASCADE, CONSTRAINT \`FK_player_spells_spell\` FOREIGN KEY (\`spell_id\`) REFERENCES \`spells\`(\`id\`) ON DELETE CASCADE ) ENGINE=InnoDB `); // --- Progression dans les voies du Dao --- await queryRunner.query(` CREATE TABLE \`player_dao_paths\` ( \`id\` VARCHAR(36) NOT NULL, \`character_id\` VARCHAR(36) NOT NULL, \`path\` VARCHAR(20) NOT NULL, \`is_primary\` TINYINT(1) NOT NULL DEFAULT 0, \`path_points\` INT NOT NULL DEFAULT 0, \`path_level\` INT NOT NULL DEFAULT 0, PRIMARY KEY (\`id\`), UNIQUE INDEX \`IDX_player_dao_char_path\` (\`character_id\`, \`path\`), INDEX \`IDX_player_dao_character\` (\`character_id\`), CONSTRAINT \`FK_player_dao_character\` FOREIGN KEY (\`character_id\`) REFERENCES \`characters\`(\`id\`) ON DELETE CASCADE ) ENGINE=InnoDB `); // --- Seed des 15 sorts --- await queryRunner.query(` INSERT INTO \`spells\` (\`id\`, \`name\`, \`path\`, \`path_level\`, \`mana_cost\`, \`cooldown\`, \`target_type\`, \`description\`, \`effects\`, \`unlock_cost\`) VALUES -- Ecoute (UUID(), 'Perception du Flux', 'ecoute', 1, 10, 3, 'enemy', 'Revele les faiblesses de l''ennemi. Buff +20% degats pendant 2 tours.', '[{"type":"buff","stat":"damage","value":20,"isPercent":true,"duration":2,"log":"{caster} percoit les failles de {target} !"}]', 0), (UUID(), 'Chant d''Eveil', 'ecoute', 2, 20, 2, 'enemy', 'Degats magiques + debuff Confusion (-30% precision, 2 tours).', '[{"type":"damage","ratio":2,"ratioStat":"intelligence","log":"{caster} entonne le Chant d''Eveil — {damage} degats !"},{"type":"debuff","stat":"precision","value":30,"isPercent":true,"duration":2,"log":"{target} est Confus !"}]', 3), (UUID(), 'Ancrage Memoriel', 'ecoute', 3, 15, 4, 'self', 'Annule le prochain debuff ou purifie un debuff actif.', '[{"type":"purge","stat":"debuff","value":1,"log":"{caster} ancre sa memoire — debuff annule !"}]', 6), (UUID(), 'Murmure du Courant', 'ecoute', 4, 25, 3, 'enemy', 'Degats magiques (Int x2.5) + drain mana si ennemi caster.', '[{"type":"damage","ratio":2.5,"ratioStat":"intelligence","log":"{caster} murmure au Courant — {damage} degats !"},{"type":"special","stat":"mana_drain","value":15,"log":"Le Courant aspire l''energie de {target} !"}]', 10), (UUID(), 'Chant de l''Oubli', 'ecoute', 5, 35, 5, 'enemy', 'Reset cooldowns ennemis + degats (Int x3). Boss : -1 buff au lieu du reset.', '[{"type":"damage","ratio":3,"ratioStat":"intelligence","log":"{caster} libere le Chant de l''Oubli — {damage} degats !"},{"type":"special","stat":"cooldown_reset","value":0,"log":"Les capacites de {target} sont perturbees !"}]', 15), -- Resonance (UUID(), 'Onde de Choc', 'resonance', 1, 15, 2, 'all_enemies', 'Degats physiques AoE (Force x1.5) a tous les ennemis.', '[{"type":"damage","ratio":1.5,"ratioStat":"force","log":"{caster} declenche une Onde de Choc — {damage} degats !"}]', 0), (UUID(), 'Bouclier de Flux', 'resonance', 2, 20, 4, 'self', 'Reduit les degats recus de 40% pendant 2 tours.', '[{"type":"buff","stat":"damage_reduction","value":40,"isPercent":true,"duration":2,"log":"{caster} erige un Bouclier de Flux !"}]', 3), (UUID(), 'Contre-Courant', 'resonance', 3, 15, 3, 'self', 'Riposte automatique au prochain coup recu (Force x2).', '[{"type":"buff","stat":"riposte","value":2,"isPercent":false,"duration":1,"log":"{caster} se prepare a la riposte !"}]', 6), (UUID(), 'Ancre de Pierre', 'resonance', 4, 25, 4, 'self', 'Taunt + boost defense 50% pendant 2 tours.', '[{"type":"buff","stat":"taunt","value":1,"isPercent":false,"duration":2,"log":"{caster} s''ancre dans la pierre !"},{"type":"buff","stat":"defense","value":50,"isPercent":true,"duration":2,"log":"Defense renforcee !"}]', 10), (UUID(), 'Fracture Sismique', 'resonance', 5, 40, 5, 'enemy', 'Degats massifs (Force x3.5) + Stun 1 tour.', '[{"type":"damage","ratio":3.5,"ratioStat":"force","log":"{caster} fracture le sol — {damage} degats !"},{"type":"debuff","stat":"stun","value":1,"isPercent":false,"duration":1,"log":"{target} est etourdi !"}]', 15), -- Harmonie (UUID(), 'Chant Apaisant', 'harmonie', 1, 15, 2, 'ally', 'Soin (Int x2 + 10% hpMax).', '[{"type":"heal","ratio":2,"ratioStat":"intelligence","value":10,"log":"{caster} entonne un chant apaisant — {target} recupere {heal} HP !"}]', 0), (UUID(), 'Dissolution', 'harmonie', 2, 20, 3, 'enemy', 'Retire tous les buffs d''un ennemi.', '[{"type":"purge","stat":"buff","value":99,"log":"{caster} dissout les protections de {target} !"}]', 3), (UUID(), 'Onde de Serenite', 'harmonie', 3, 25, 4, 'all_allies', 'Buff defense +25% + regen 5% hpMax/tour (3 tours) a toute l''equipe.', '[{"type":"buff","stat":"defense","value":25,"isPercent":true,"duration":3,"log":"Une onde de serenite enveloppe l''equipe !"},{"type":"buff","stat":"regen","value":5,"isPercent":true,"duration":3,"log":"Regeneration active !"}]', 6), (UUID(), 'Lien du Courant', 'harmonie', 4, 20, 3, 'ally', 'Transfere 30% des degats du joueur au compagnon (ou inverse) pendant 3 tours.', '[{"type":"buff","stat":"damage_link","value":30,"isPercent":true,"duration":3,"log":"{caster} tisse un lien de Courant avec {target} !"}]', 10), (UUID(), 'Symphonie Restauratrice', 'harmonie', 5, 45, 6, 'all_allies', 'Full heal equipe + purge tous debuffs + bouclier 1 coup.', '[{"type":"heal","ratio":0,"ratioStat":"intelligence","value":100,"log":"La Symphonie Restauratrice guerit toute l''equipe !"},{"type":"purge","stat":"debuff","value":99,"log":"Tous les debuffs sont purges !"},{"type":"buff","stat":"shield","value":1,"isPercent":false,"duration":1,"log":"Un bouclier protege chacun du prochain coup !"}]', 15) `); } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`DROP TABLE IF EXISTS \`player_spells\``); await queryRunner.query(`DROP TABLE IF EXISTS \`player_dao_paths\``); await queryRunner.query(`DROP TABLE IF EXISTS \`spells\``); await queryRunner.query(`ALTER TABLE \`monsters\` DROP COLUMN \`is_boss\`, DROP COLUMN \`ai_profile\``); await queryRunner.query(`ALTER TABLE \`characters\` DROP COLUMN \`mana_max\`, DROP COLUMN \`mana_current\``); } }