diff --git a/.gitignore b/.gitignore index 8c2a9f54f..2b95adf2c 100644 --- a/.gitignore +++ b/.gitignore @@ -29,5 +29,6 @@ public/images/pokemon/input/*.png public/images/pokemon/input/output/* public/images/pokemon/icons/input/*.png public/images/pokemon/icons/input/output/* +public/images/character/*/ src/data/battle-anim-raw-data*.ts src/data/battle-anim-data.ts diff --git a/public/images/character/rival_f.json b/public/images/character/rival_f.json new file mode 100644 index 000000000..2a9014b17 --- /dev/null +++ b/public/images/character/rival_f.json @@ -0,0 +1,209 @@ +{ + "textures": [ + { + "image": "rival_f.png", + "format": "RGBA8888", + "size": { + "w": 184, + "h": 675 + }, + "scale": 1, + "frames": [ + { + "filename": "angry", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 108, + "h": 135 + }, + "spriteSourceSize": { + "x": 9, + "y": 0, + "w": 92, + "h": 135 + }, + "frame": { + "x": 0, + "y": 0, + "w": 92, + "h": 135 + } + }, + { + "filename": "angry_mopen", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 108, + "h": 135 + }, + "spriteSourceSize": { + "x": 9, + "y": 0, + "w": 92, + "h": 135 + }, + "frame": { + "x": 92, + "y": 0, + "w": 92, + "h": 135 + } + }, + { + "filename": "neutral", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 108, + "h": 135 + }, + "spriteSourceSize": { + "x": 9, + "y": 0, + "w": 92, + "h": 135 + }, + "frame": { + "x": 0, + "y": 135, + "w": 92, + "h": 135 + } + }, + { + "filename": "shock", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 108, + "h": 135 + }, + "spriteSourceSize": { + "x": 9, + "y": 0, + "w": 92, + "h": 135 + }, + "frame": { + "x": 92, + "y": 135, + "w": 92, + "h": 135 + } + }, + { + "filename": "smile", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 108, + "h": 135 + }, + "spriteSourceSize": { + "x": 9, + "y": 0, + "w": 92, + "h": 135 + }, + "frame": { + "x": 0, + "y": 270, + "w": 92, + "h": 135 + } + }, + { + "filename": "smile_eclosed", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 108, + "h": 135 + }, + "spriteSourceSize": { + "x": 9, + "y": 0, + "w": 92, + "h": 135 + }, + "frame": { + "x": 92, + "y": 270, + "w": 92, + "h": 135 + } + }, + { + "filename": "smile_ehalf", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 108, + "h": 135 + }, + "spriteSourceSize": { + "x": 9, + "y": 0, + "w": 92, + "h": 135 + }, + "frame": { + "x": 0, + "y": 405, + "w": 92, + "h": 135 + } + }, + { + "filename": "smile_wave", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 108, + "h": 135 + }, + "spriteSourceSize": { + "x": 18, + "y": 0, + "w": 83, + "h": 135 + }, + "frame": { + "x": 0, + "y": 540, + "w": 83, + "h": 135 + } + }, + { + "filename": "smile_wave_wink", + "rotated": false, + "trimmed": true, + "sourceSize": { + "w": 108, + "h": 135 + }, + "spriteSourceSize": { + "x": 18, + "y": 0, + "w": 83, + "h": 135 + }, + "frame": { + "x": 83, + "y": 540, + "w": 83, + "h": 135 + } + } + ] + } + ], + "meta": { + "app": "https://www.codeandweb.com/texturepacker", + "version": "3.0", + "smartupdate": "$TexturePacker:SmartUpdate:1be996f9906a409496e360d67e49ccd0:db1900b6808dc32ed4746255b60aa557:0aaa288a75ecf87b6647cbb7fd0d2ecc$" + } +} diff --git a/public/images/character/rival_f.png b/public/images/character/rival_f.png new file mode 100644 index 000000000..b165cd8a7 Binary files /dev/null and b/public/images/character/rival_f.png differ diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 4c2c8f556..cd8e6ae97 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -51,6 +51,7 @@ import { FormChangePhase, QuietFormChangePhase } from './form-change-phase'; import { BattleSpec } from './enums/battle-spec'; import { getTypeRgb } from './data/type'; import PokemonSpriteSparkleHandler from './sprite/pokemon-sprite-sparkle-handler'; +import CharSprite from './ui/char-sprite'; const enableAuto = true; const quickStart = false; @@ -112,6 +113,7 @@ export default class BattleScene extends Phaser.Scene { private standbyPhase: Phase; public field: Phaser.GameObjects.Container; public fieldUI: Phaser.GameObjects.Container; + public charSprite: CharSprite; public pbTray: PokeballTray; public pbTrayEnemy: PokeballTray; public abilityBar: AbilityBar; @@ -320,6 +322,9 @@ export default class BattleScene extends Phaser.Scene { this.loadAtlas(config.getKey(true), 'trainer'); }); + // Load character sprites + this.loadAtlas('c_rival_f', 'character', 'rival_f'); + // Load pokemon-related images this.loadImage(`pkmn__back__sub`, 'pokemon/back', 'sub.png'); this.loadImage(`pkmn__sub`, 'pokemon', 'sub.png'); @@ -483,6 +488,11 @@ export default class BattleScene extends Phaser.Scene { this.add.existing(this.enemyModifierBar); uiContainer.add(this.enemyModifierBar); + this.charSprite = new CharSprite(this); + this.charSprite.setup(); + + this.fieldUI.add(this.charSprite); + this.pbTray = new PokeballTray(this, true); this.pbTray.setup(); @@ -850,7 +860,7 @@ export default class BattleScene extends Phaser.Scene { this.currentBattle = new Battle(this.gameMode, newWaveIndex, newBattleType, newTrainer, newDouble); this.currentBattle.incrementTurn(this); - //this.pushPhase(new TrainerMessageTestPhase(this)); + //this.pushPhase(new TrainerMessageTestPhase(this, TrainerType.RIVAL, TrainerType.RIVAL_2, TrainerType.RIVAL_3, TrainerType.RIVAL_4, TrainerType.RIVAL_5, TrainerType.RIVAL_6)); if (!waveIndex && lastBattle) { const isNewBiome = !(lastBattle.waveIndex % 10); diff --git a/src/data/dialogue.ts b/src/data/dialogue.ts index c98561740..4aa6199f7 100644 --- a/src/data/dialogue.ts +++ b/src/data/dialogue.ts @@ -334,7 +334,7 @@ export const trainerTypeDialogue = { `Hey, I was looking for you! I knew you were eager to get going but I expected at least a goodbye… $So you're really pursuing your dream after all?\n I almost can't believe it. $Since we're here, how about a battle?\nAfter all, I want to make sure you're ready. - $Don't hold back, I want you to give me everything you've got!` + $@c{angry_mopen}Don't hold back, I want you to give me everything you've got!` ], victory: [ `Wow… You actually cleaned me out.\nAre you actually a beginner? @@ -345,16 +345,16 @@ export const trainerTypeDialogue = { }, { encounter: [ - `There you are! I've been looking everywhere for you!\nDid you forget to say goodbye to your best friend? - $You're going after your dream, huh?\nThat day is really today isn't it… - $Anyway, I'll forgive you for forgetting me, but on one condition. You have to battle me! - $Give it your all! Wouldn't want your adventure to be over before it started, right?` + `@c{smile_wave}There you are! I've been looking everywhere for you!\n@c{angry_mopen}Did you forget to say goodbye to your best friend? + $@c{smile_ehalf}You're going after your dream, huh?\nThat day is really today isn't it… + $@c{smile}Anyway, I'll forgive you for forgetting me, but on one condition. @c{smile_wave_wink}You have to battle me! + $@c{angry_mopen}Give it your all! Wouldn't want your adventure to be over before it started, right?` ], victory: [ - `You just started and you're already this strong?!\nYou totally cheated, didn't you? - $Just kidding! I lost fair and square… I have a feeling you're going to do really well out there. - $By the way, the professor wanted me to give you some items. Hopefully they're helpful! - $Do your best like always! I believe in you!` + `@c{shock}You just started and you're already this strong?!\n@c{angry}@d{96}You totally cheated, didn't you? + $@c{smile_wave_wink}Just kidding! @d{64}@c{smile_eclosed}I lost fair and square… I have a feeling you're going to do really well out there. + $@c{smile}By the way, the professor wanted me to give you some items. Hopefully they're helpful! + $@c{smile_wave}Do your best like always! I believe in you!` ] } ], @@ -373,14 +373,14 @@ export const trainerTypeDialogue = { }, { encounter: [ - `Oh, fancy meeting you here. Looks like you're still undefeated. Huh… Not bad! - $I know what you're thinking, and no, I wasn't creeping on you. I just happened to be in the area. - $I'm happy for you but I just want to let you know that it's OK to lose sometimes. - $We learn from our mistakes, often more than we would if we kept succeeding. - $In any case, I've been training hard for our rematch, so you'd better give it your all!` + `@c{smile_wave}Oh, fancy meeting you here. Looks like you're still undefeated. @c{angry_mopen}Huh… Not bad! + $@c{angry_mopen}I know what you're thinking, and no, I wasn't creeping on you. @c{smile_eclosed}I just happened to be in the area. + $@c{smile_ehalf}I'm happy for you but I just want to let you know that it's OK to lose sometimes. + $@c{smile}We learn from our mistakes, often more than we would if we kept succeeding. + $@c{angry_mopen}In any case, I've been training hard for our rematch, so you'd better give it your all!` ], victory: [ - `I… wasn't supposed to lose that time…` + `@c{neutral}I… wasn't supposed to lose that time…` ] } ], @@ -400,13 +400,13 @@ export const trainerTypeDialogue = { }, { encounter: [ - `Long time no see! Still haven't lost, huh.\nYou're starting to get on my nerves. Just kidding! - $But really, don't you miss home by now? Or… me?\nI… I mean, we've really missed you. - $I support you in your dream and everything, but the reality is you're going to lose sooner or later. - $And when you do, I'll be there for you like always.\nNow, let me show you how strong I've become!` + `@c{smile_wave}Long time no see! Still haven't lost, huh.\n@c{angry}You're starting to get on my nerves. @c{smile_wave_wink}Just kidding! + $@c{smile_ehalf}But really, don't you miss home by now? Or… me?\nI… I mean, we've really missed you. + $@c{smile_eclosed}I support you in your dream and everything, but the reality is you're going to lose sooner or later. + $@c{smile}And when you do, I'll be there for you like always.\n@c{angry_mopen}Now, let me show you how strong I've become!` ], victory: [ - `After all that… it wasn't enough…?\nYou'll never come back at this rate…` + `@c{shock}After all that… it wasn't enough…?\nYou'll never come back at this rate…` ] } ], @@ -428,17 +428,17 @@ export const trainerTypeDialogue = { }, { encounter: [ - `It's me! You didn't forget about me again… did you? - $You should be proud of how far you made it. Congrats!\nBut it looks like it's the end of your journey. - $You've awoken something in me I never knew was there.\nIt seems like all I do now is train. - $I hardly even eat or sleep now, I just train my Pokémon all day, getting stronger every time. - $In fact, I… hardly recognize myself. + `@c{neutral}It's me! You didn't forget about me again… did you? + $@c{smile}You should be proud of how far you made it. Congrats!\nBut it looks like it's the end of your journey. + $@c{smile_eclosed}You've awoken something in me I never knew was there.\nIt seems like all I do now is train. + $@c{smile_ehalf}I hardly even eat or sleep now, I just train my Pokémon all day, getting stronger every time. + $@c{neutral}In fact, I… hardly recognize myself. $And now, I've finally reached peak performance.\nI don't think anyone could beat me now. - $And you know what? It's all because of you.\nI don't know whether to thank you or hate you. - $Prepare yourself.` + $And you know what? It's all because of you.\n@c{smile_ehalf}I don't know whether to thank you or hate you. + $@c{angry_mopen}Prepare yourself.` ], victory: [ - `What…@d{64} What are you?` + `@c{neutral}What…@d{64} What are you?` ] } ], @@ -470,25 +470,25 @@ export const trainerTypeDialogue = { }, { encounter: [ - `So it's just us again. - $You know, I keep going around and around in my head… - $There's something to all this, why everything seems so strange now… - $You have your dream, and I have this ambition in me… + `@c{smile_ehalf}So it's just us again. + $@c{smile_eclosed}You know, I keep going around and around in my head… + $@c{smile_ehalf}There's something to all this, why everything seems so strange now… + $@c{smile}You have your dream, and I have this ambition in me… $I just can't help but feel there's a greater purpose to all this, to what we're doing, you and I. - $I think I'm supposed to push you… to your limits. - $I'm not sure if I've been doing a good job at that, but I've tried my best up to now. + $@c{smile_eclosed}I think I'm supposed to push you… to your limits. + $@c{smile_ehalf}I'm not sure if I've been doing a good job at that, but I've tried my best up to now. $It's something about this strange and dreadful place… Everything seems so clear… $This… is all the world's known for a long time now. - $It's like I can barely remember the memories we cherished together. - $Were they even real? They seem so far away now… - $You need to keep pushing, because if you don't, it will never end. You're the only one who can do this. + $@c{smile_eclosed}It's like I can barely remember the memories we cherished together. + $@c{smile_ehalf}Were they even real? They seem so far away now… + $@c{angry_mopen}You need to keep pushing, because if you don't, it will never end. You're the only one who can do this. $I… don't know what all this means… but I feel it's true. $If you can't defeat me here and now, you won't stand a chance.` ], victory: [ - `I… I think I fulfilled my purpose… - $Promise me… After you heal the world… Please… come home safe. - $…Thank you.` + `@c{smile_ehalf}I… I think I fulfilled my purpose… + $@c{smile_eclosed}Promise me… After you heal the world… Please… come home safe. + $@c{smile_ehalf}…Thank you.` ] } ] diff --git a/src/data/trainer-config.ts b/src/data/trainer-config.ts index 06c460a54..4aa37b84c 100644 --- a/src/data/trainer-config.ts +++ b/src/data/trainer-config.ts @@ -176,6 +176,7 @@ export class TrainerConfig { public nameFemale: string; public title: string; public hasGenders: boolean = false; + public hasCharSprite: boolean = false; public isDouble: boolean = false; public moneyMultiplier: number = 1; public isBoss: boolean = false; @@ -257,6 +258,11 @@ export class TrainerConfig { return this; } + setHasCharSprite(): TrainerConfig { + this.hasCharSprite = true; + return this; + } + setDouble(): TrainerConfig { this.isDouble = true; return this; @@ -763,19 +769,19 @@ export const trainerConfigs: TrainerConfigs = { [TrainerType.NEMONA]: new TrainerConfig(++t).initForChampion([ Species.LYCANROC, Species.KORAIDON, Species.KOMMO_O, Species.PAWMOT, Species.DUSKNOIR ]), [TrainerType.KIERAN]: new TrainerConfig(++t).initForChampion([ Species.POLITOED, Species.TERAPAGOS, Species.HYDRAPPLE, Species.PORYGON_Z, Species.GRIMMSNARL ]), - [TrainerType.RIVAL]: new TrainerConfig((t = TrainerType.RIVAL)).setName('Finn').setHasGenders('Ivy').setTitle('Rival').setStaticParty().setEncounterBgm(TrainerType.RIVAL).setBattleBgm('battle_rival').setPartyTemplates(trainerPartyTemplates.RIVAL) + [TrainerType.RIVAL]: new TrainerConfig((t = TrainerType.RIVAL)).setName('Finn').setHasGenders('Ivy').setHasCharSprite().setTitle('Rival').setStaticParty().setEncounterBgm(TrainerType.RIVAL).setBattleBgm('battle_rival').setPartyTemplates(trainerPartyTemplates.RIVAL) .setModifierRewardFuncs(() => modifierTypes.SUPER_EXP_CHARM, () => modifierTypes.EXP_SHARE).setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE, Species.CHIKORITA, Species.CYNDAQUIL, Species.TOTODILE, Species.TREECKO, Species.TORCHIC, Species.MUDKIP, Species.TURTWIG, Species.CHIMCHAR, Species.PIPLUP, Species.SNIVY, Species.TEPIG, Species.OSHAWOTT, Species.CHESPIN, Species.FENNEKIN, Species.FROAKIE, Species.ROWLET, Species.LITTEN, Species.POPPLIO, Species.GROOKEY, Species.SCORBUNNY, Species.SOBBLE, Species.SPRIGATITO, Species.FUECOCO, Species.QUAXLY ])) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEY, Species.HOOTHOOT, Species.TAILLOW, Species.STARLY, Species.PIDOVE, Species.FLETCHLING, Species.PIKIPEK, Species.ROOKIDEE, Species.WATTREL ])), - [TrainerType.RIVAL_2]: new TrainerConfig(++t).setName('Finn').setHasGenders('Ivy').setTitle('Rival').setStaticParty().setMoneyMultiplier(1.25).setEncounterBgm(TrainerType.RIVAL).setBattleBgm('battle_rival').setPartyTemplates(trainerPartyTemplates.RIVAL_2) + [TrainerType.RIVAL_2]: new TrainerConfig(++t).setName('Finn').setHasGenders('Ivy').setHasCharSprite().setTitle('Rival').setStaticParty().setMoneyMultiplier(1.25).setEncounterBgm(TrainerType.RIVAL).setBattleBgm('battle_rival').setPartyTemplates(trainerPartyTemplates.RIVAL_2) .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.IVYSAUR, Species.CHARMELEON, Species.WARTORTLE, Species.BAYLEEF, Species.QUILAVA, Species.CROCONAW, Species.GROVYLE, Species.COMBUSKEN, Species.MARSHTOMP, Species.GROTLE, Species.MONFERNO, Species.PRINPLUP, Species.SERVINE, Species.PIGNITE, Species.DEWOTT, Species.QUILLADIN, Species.BRAIXEN, Species.FROGADIER, Species.DARTRIX, Species.TORRACAT, Species.BRIONNE, Species.THWACKEY, Species.RABOOT, Species.DRIZZILE, Species.FLORAGATO, Species.CROCALOR, Species.QUAXWELL ])) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEOTTO, Species.HOOTHOOT, Species.TAILLOW, Species.STARAVIA, Species.TRANQUILL, Species.FLETCHINDER, Species.TRUMBEAK, Species.CORVISQUIRE, Species.WATTREL ])) .setPartyMemberFunc(2, getSpeciesFilterRandomPartyMemberFunc((species: PokemonSpecies) => !pokemonEvolutions.hasOwnProperty(species.speciesId) && !pokemonPrevolutions.hasOwnProperty(species.speciesId) && species.baseTotal >= 450)), - [TrainerType.RIVAL_3]: new TrainerConfig(++t).setName('Finn').setHasGenders('Ivy').setTitle('Rival').setStaticParty().setMoneyMultiplier(1.5).setEncounterBgm(TrainerType.RIVAL).setBattleBgm('battle_rival').setPartyTemplates(trainerPartyTemplates.RIVAL_3) + [TrainerType.RIVAL_3]: new TrainerConfig(++t).setName('Finn').setHasGenders('Ivy').setHasCharSprite().setTitle('Rival').setStaticParty().setMoneyMultiplier(1.5).setEncounterBgm(TrainerType.RIVAL).setBattleBgm('battle_rival').setPartyTemplates(trainerPartyTemplates.RIVAL_3) .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL ])) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEOT, Species.NOCTOWL, Species.SWELLOW, Species.STARAPTOR, Species.UNFEZANT, Species.TALONFLAME, Species.TOUCANNON, Species.CORVIKNIGHT, Species.KILOWATTREL ])) .setPartyMemberFunc(2, getSpeciesFilterRandomPartyMemberFunc((species: PokemonSpecies) => !pokemonEvolutions.hasOwnProperty(species.speciesId) && !pokemonPrevolutions.hasOwnProperty(species.speciesId) && species.baseTotal >= 450)) .setSpeciesFilter(species => species.baseTotal >= 540), - [TrainerType.RIVAL_4]: new TrainerConfig(++t).setName('Finn').setHasGenders('Ivy').setTitle('Rival').setBoss().setStaticParty().setMoneyMultiplier(1.75).setEncounterBgm(TrainerType.RIVAL).setBattleBgm('battle_rival_2').setPartyTemplates(trainerPartyTemplates.RIVAL_4) + [TrainerType.RIVAL_4]: new TrainerConfig(++t).setName('Finn').setHasGenders('Ivy').setHasCharSprite().setTitle('Rival').setBoss().setStaticParty().setMoneyMultiplier(1.75).setEncounterBgm(TrainerType.RIVAL).setBattleBgm('battle_rival_2').setPartyTemplates(trainerPartyTemplates.RIVAL_4) .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL ])) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEOT, Species.NOCTOWL, Species.SWELLOW, Species.STARAPTOR, Species.UNFEZANT, Species.TALONFLAME, Species.TOUCANNON, Species.CORVIKNIGHT, Species.KILOWATTREL ])) .setPartyMemberFunc(2, getSpeciesFilterRandomPartyMemberFunc((species: PokemonSpecies) => !pokemonEvolutions.hasOwnProperty(species.speciesId) && !pokemonPrevolutions.hasOwnProperty(species.speciesId) && species.baseTotal >= 450)) @@ -784,7 +790,7 @@ export const trainerConfigs: TrainerConfigs = { const starter = party[0]; return [ modifierTypes.TERA_SHARD().generateType(null, [ starter.species.type1 ]).withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(starter) as PersistentModifier ]; }), - [TrainerType.RIVAL_5]: new TrainerConfig(++t).setName('Finn').setHasGenders('Ivy').setTitle('Rival').setBoss().setStaticParty().setMoneyMultiplier(2.25).setEncounterBgm(TrainerType.RIVAL).setBattleBgm('battle_rival_3').setPartyTemplates(trainerPartyTemplates.RIVAL_5) + [TrainerType.RIVAL_5]: new TrainerConfig(++t).setName('Finn').setHasGenders('Ivy').setHasCharSprite().setTitle('Rival').setBoss().setStaticParty().setMoneyMultiplier(2.25).setEncounterBgm(TrainerType.RIVAL).setBattleBgm('battle_rival_3').setPartyTemplates(trainerPartyTemplates.RIVAL_5) .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL ])) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEOT, Species.NOCTOWL, Species.SWELLOW, Species.STARAPTOR, Species.UNFEZANT, Species.TALONFLAME, Species.TOUCANNON, Species.CORVIKNIGHT, Species.KILOWATTREL ])) .setPartyMemberFunc(2, getSpeciesFilterRandomPartyMemberFunc((species: PokemonSpecies) => !pokemonEvolutions.hasOwnProperty(species.speciesId) && !pokemonPrevolutions.hasOwnProperty(species.speciesId) && species.baseTotal >= 450)) @@ -797,7 +803,7 @@ export const trainerConfigs: TrainerConfigs = { const starter = party[0]; return [ modifierTypes.TERA_SHARD().generateType(null, [ starter.species.type1 ]).withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(starter) as PersistentModifier ]; }), - [TrainerType.RIVAL_6]: new TrainerConfig(++t).setName('Finn').setHasGenders('Ivy').setTitle('Rival').setBoss().setStaticParty().setMoneyMultiplier(3).setEncounterBgm('final').setBattleBgm('battle_rival_3').setPartyTemplates(trainerPartyTemplates.RIVAL_6) + [TrainerType.RIVAL_6]: new TrainerConfig(++t).setName('Finn').setHasGenders('Ivy').setHasCharSprite().setTitle('Rival').setBoss().setStaticParty().setMoneyMultiplier(3).setEncounterBgm('final').setBattleBgm('battle_rival_3').setPartyTemplates(trainerPartyTemplates.RIVAL_6) .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON ])) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEOT, Species.NOCTOWL, Species.SWELLOW, Species.STARAPTOR, Species.UNFEZANT, Species.TALONFLAME, Species.TOUCANNON, Species.CORVIKNIGHT ])) .setPartyMemberFunc(2, getSpeciesFilterRandomPartyMemberFunc((species: PokemonSpecies) => !pokemonEvolutions.hasOwnProperty(species.speciesId) && !pokemonPrevolutions.hasOwnProperty(species.speciesId) && species.baseTotal >= 450)) diff --git a/src/phases.ts b/src/phases.ts index 12b60424f..37295d5dd 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -620,9 +620,17 @@ export class EncounterPhase extends BattlePhase { if (!encounterMessages?.length) doSummon(); else { - let message: string; - this.scene.executeWithSeedOffset(() => message = Utils.randSeedItem(encounterMessages), this.scene.currentBattle.waveIndex); - this.scene.ui.showDialogue(message, trainer.getName(), null, doSummon); + const showDialogueAndSummon = () => { + let message: string; + this.scene.executeWithSeedOffset(() => message = Utils.randSeedItem(encounterMessages), this.scene.currentBattle.waveIndex); + this.scene.ui.showDialogue(message, trainer.getName(), null, () => { + this.scene.charSprite.hide().then(() => this.scene.hideFieldOverlay(250).then(() => doSummon())); + }); + }; + if (this.scene.currentBattle.trainer.config.hasCharSprite) + this.scene.showFieldOverlay(500).then(() => this.scene.charSprite.showCharacter(trainer.getKey(), 'smile').then(() => showDialogueAndSummon())); + else + showDialogueAndSummon(); } } } diff --git a/src/ui/char-sprite.ts b/src/ui/char-sprite.ts new file mode 100644 index 000000000..872e8deac --- /dev/null +++ b/src/ui/char-sprite.ts @@ -0,0 +1,106 @@ +import BattleScene from "../battle-scene"; + +export default class CharSprite extends Phaser.GameObjects.Container { + private sprite: Phaser.GameObjects.Sprite; + private transitionSprite: Phaser.GameObjects.Sprite; + + public key: string; + public variant: string; + public shown: boolean; + + constructor(scene: BattleScene) { + super(scene, (scene.game.canvas.width / 6) + 32, -42); + } + + setup(): void { + [ this.sprite, this.transitionSprite ] = new Array(2).fill(null).map(() => { + const ret = this.scene.add.sprite(0, 0, '', ''); + ret.setOrigin(0.5, 1); + this.add(ret); + return ret; + }); + + this.transitionSprite.setVisible(false); + + this.setVisible(false); + this.shown = false; + } + + showCharacter(key: string, variant: string): Promise { + return new Promise(resolve => { + if (!key.startsWith('c_')) + key = `c_${key}`; + if (this.shown) { + if (key === this.key && variant === this.variant) + return resolve(); + if (key !== this.key) + return this.hide().then(() => this.showCharacter(key, variant)); + this.setVariant(variant).then(() => resolve()); + return; + } + + this.sprite.setTexture(key, variant); + + (this.scene as BattleScene).fieldUI.bringToTop(this); + + this.scene.tweens.add({ + targets: this, + x: (this.scene.game.canvas.width / 6) - 102, + duration: 750, + ease: 'Cubic.easeOut', + onComplete: () => { + resolve(); + } + }); + + this.setVisible(this.scene.textures.get(key).key !== '__MISSING'); + this.shown = true; + + this.key = key; + this.variant = variant; + }); + } + + setVariant(variant: string): Promise { + return new Promise(resolve => { + (this.scene as BattleScene).fieldUI.bringToTop(this); + + this.transitionSprite.setTexture(this.key, variant); + this.transitionSprite.setAlpha(0); + this.transitionSprite.setVisible(true); + this.scene.tweens.add({ + targets: this.transitionSprite, + alpha: 1, + duration: 250, + ease: 'Sine.easeIn', + onComplete: () => { + this.sprite.setTexture(this.key, variant); + this.transitionSprite.setVisible(false); + resolve(); + } + }); + this.variant = variant; + }); + } + + hide(): Promise { + return new Promise(resolve => { + if (!this.shown) + return resolve(); + + this.scene.tweens.add({ + targets: this, + x: (this.scene.game.canvas.width / 6) + 32, + duration: 750, + ease: 'Cubic.easeIn', + onComplete: () => { + if (!this.shown) + this.setVisible(false); + resolve(); + } + }); + + this.shown = false; + }); + }; +} \ No newline at end of file diff --git a/src/ui/message-ui-handler.ts b/src/ui/message-ui-handler.ts index faeeb9079..7fd61c829 100644 --- a/src/ui/message-ui-handler.ts +++ b/src/ui/message-ui-handler.ts @@ -28,12 +28,16 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler { private showTextInternal(text: string, delay: integer, callback: Function, callbackDelay: integer, prompt: boolean, promptDelay: integer) { if (delay === null || delay === undefined) delay = 20; + let charVarMap = new Map(); let delayMap = new Map(); let soundMap = new Map(); - const actionPattern = /@(d|s)\{(.*?)\}/; + const actionPattern = /@(c|d|s)\{(.*?)\}/; let actionMatch: RegExpExecArray; while ((actionMatch = actionPattern.exec(text))) { switch (actionMatch[1]) { + case 'c': + charVarMap.set(actionMatch.index, actionMatch[2]); + break; case 'd': delayMap.set(actionMatch.index, parseInt(actionMatch[2])); break; @@ -67,10 +71,13 @@ export default abstract class MessageUiHandler extends AwaitableUiHandler { delay: delay, callback: () => { const charIndex = text.length - this.textTimer.repeatCount; + const charVar = charVarMap.get(charIndex); const charSound = soundMap.get(charIndex); const charDelay = delayMap.get(charIndex); this.message.setText(text.slice(0, charIndex)); const advance = () => { + if (charVar) + this.scene.charSprite.setVariant(charVar); if (charSound) this.scene.playSound(charSound); if (callback && !this.textTimer.repeatCount) {