From d251a9d516dab116f158b387cfc14a79a38aad19 Mon Sep 17 00:00:00 2001 From: hexblit Date: Sat, 11 May 2024 18:41:01 -0600 Subject: [PATCH] websocket communication resolved and auto continue done so chat doesnt have to spam 'a' accept. --- .env.development | 2 +- package-lock.json | 29 +++++++- package.json | 4 +- src/battle-scene.ts | 5 +- src/inputs-controller.ts | 145 ++++++++++++++++++++++++++++++++++----- src/phases.ts | 34 +++++++-- src/ui-inputs.ts | 5 ++ vite.config.js | 2 +- 8 files changed, 196 insertions(+), 30 deletions(-) diff --git a/.env.development b/.env.development index 88dcdce61..4ce9e53b2 100644 --- a/.env.development +++ b/.env.development @@ -1,2 +1,2 @@ VITE_BYPASS_LOGIN=1 -VITE_BYPASS_TUTORIAL=0 \ No newline at end of file +VITE_BYPASS_TUTORIAL=1 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index cdcf19ffa..75a3ad381 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,12 +10,12 @@ "dependencies": { "@material/material-color-utilities": "^0.2.7", "crypto-js": "^4.2.0", + "http": "^0.0.1-security", "i18next": "^23.11.1", "i18next-browser-languagedetector": "^7.2.1", "json-stable-stringify": "^1.1.0", "phaser": "^3.70.0", - "phaser3-rex-plugins": "^1.1.84", - "ws": "^8.17.0" + "phaser3-rex-plugins": "^1.1.84" }, "devDependencies": { "@vitest/coverage-istanbul": "^1.4.0", @@ -1235,6 +1235,17 @@ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, + "node_modules/@types/node": { + "version": "20.12.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.11.tgz", + "integrity": "sha512-vDg9PZ/zi+Nqp6boSOT7plNuthRugEKixDv5sFTIpkE89MmNtEArAShI4mxuX2+UrLEe9pxC1vm2cjm9YlWbJw==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", @@ -2679,6 +2690,11 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/http": { + "version": "0.0.1-security", + "resolved": "https://registry.npmjs.org/http/-/http-0.0.1-security.tgz", + "integrity": "sha512-RnDvP10Ty9FxqOtPZuxtebw1j4L/WiqNMDtuc1YMH1XQm5TgDRaR1G9u8upL6KD1bXHSp9eSXo/ED+8Q7FAr+g==" + }, "node_modules/http-assert": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz", @@ -4554,6 +4570,14 @@ "integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==", "dev": true }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "optional": true, + "peer": true + }, "node_modules/universalify": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", @@ -5864,6 +5888,7 @@ "version": "8.17.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", + "dev": true, "engines": { "node": ">=10.0.0" }, diff --git a/package.json b/package.json index d439c3343..1b27ea802 100644 --- a/package.json +++ b/package.json @@ -30,12 +30,12 @@ "dependencies": { "@material/material-color-utilities": "^0.2.7", "crypto-js": "^4.2.0", + "http": "^0.0.1-security", "i18next": "^23.11.1", "i18next-browser-languagedetector": "^7.2.1", "json-stable-stringify": "^1.1.0", "phaser": "^3.70.0", - "phaser3-rex-plugins": "^1.1.84", - "ws": "^8.17.0" + "phaser3-rex-plugins": "^1.1.84" }, "engines": { "node": ">=18.0.0" diff --git a/src/battle-scene.ts b/src/battle-scene.ts index f117615e5..3e71d506a 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -187,9 +187,9 @@ export default class BattleScene extends SceneBase { this.phaseQueue = []; this.phaseQueuePrepend = []; this.phaseQueuePrependSpliceIndex = -1; - this.nextCommandPhaseQueue = []; + this.nextCommandPhaseQueue = []; } - + loadPokemonAtlas(key: string, atlasPath: string, experimental?: boolean) { if (experimental === undefined) experimental = this.experimentalSprites; @@ -1505,6 +1505,7 @@ export default class BattleScene extends SceneBase { this.populatePhaseQueue(); this.currentPhase = this.phaseQueue.shift(); this.currentPhase.start(); + } overridePhase(phase: Phase): boolean { diff --git a/src/inputs-controller.ts b/src/inputs-controller.ts index f1c236855..e0feac48e 100644 --- a/src/inputs-controller.ts +++ b/src/inputs-controller.ts @@ -1,5 +1,4 @@ import Phaser, {Time} from "phaser"; -import WebSocket, {WebSocketServer} from 'ws'; import * as Utils from "./utils"; import {initTouchControls} from './touch-controls'; import pad_generic from "./configs/pad_generic"; @@ -7,6 +6,8 @@ import pad_unlicensedSNES from "./configs/pad_unlicensedSNES"; import pad_xbox360 from "./configs/pad_xbox360"; import pad_dualshock from "./configs/pad_dualshock"; import {Button} from "./enums/buttons"; +import BattleScene from "./battle-scene"; +import { AttemptCapturePhase, EncounterPhase, LearnMovePhase, MessagePhase } from "./phases"; export interface GamepadMapping { [key: string]: number; @@ -28,7 +29,9 @@ export class InputsController { private buttonKeys: Phaser.Input.Keyboard.Key[][]; private gamepads: Array = new Array(); private scene: Phaser.Scene; - private wss = new WebSocketServer({ port: 9876}); + private ws: WebSocket; + + // buttonLock ensures only a single movement key is firing repeated inputs // (i.e. by holding down a button) at a time @@ -81,17 +84,123 @@ export class InputsController { this.scene.input.gamepad.on('up', this.gamepadButtonUp, this); } + + // Keyboard this.setupKeyboardControls(); + this.ws = new WebSocket("ws://127.0.0.1:5050/"); + this.ws.onopen = this.onOpen.bind(this); + this.ws.onmessage = this.onMessage.bind(this); + this.ws.onerror = this.onError.bind(this); + this.ws.onclose = this.onClose.bind(this); + - // setup WebSocket - this.wss.on('connection', function connection(ws: WebSocket) { - ws.on('message', function incoming(message: string) { - console.log('received: %s', message); + } + + private onOpen(): void { + console.log("WebSocket connection established"); + var getActions = { + "request": "GetEvents", + "id": "pokerogue" + } + + var subscribe = { + "request": "Subscribe", + "id": "pokerogue", + "events": { + "raw": [ + "ActionCompleted" + ] + }, + + } + // console.log(JSON.stringify(subscribe)); + this.ws.send(JSON.stringify(subscribe)); + } + + private onMessage(event: MessageEvent): void { + var data = JSON.parse(event.data); + // console.log("Received message: ", data.data); + + if ( data.data.name == "twitch-plays-input-decide") { + var chatAction = data.data.arguments.chatAction + + if (chatAction != "NoOp") { + this.processInputCommad(chatAction); + } else { - }); - - }); + let battleScene = this.scene as BattleScene; + console.log(battleScene); + if (battleScene != null) { + let currentPhase = battleScene.getCurrentPhase(); + switch (currentPhase.constructor.name){ // AttemptCapturePhase + case 'ExpPhase': + case 'EvolutionPhase': + case 'ModifierRewardPhase': + case 'LevelUpPhase': + this.processInputCommad('accept') + break; + case 'MessagePhase': + console.log(battleScene); + let messagePhase = currentPhase as MessagePhase; + if(messagePhase.getText().includes("fainted!") + || messagePhase.getText().includes("fled!") + || messagePhase.getText().includes("You picked up")) { + this.processInputCommad('accept') + } + break; + case 'LearnMovePhase': + console.log(battleScene); + let learnMovePhase = currentPhase as LearnMovePhase; + if(learnMovePhase.openMovesRemaining > 0) { // only auto continue if move slot is free + this.processInputCommad('accept') + } else { + switch(learnMovePhase.learnMovesStep) { + case 'battle:learnMovePrompt': + case 'battle:learnMoveLimitReached': + case 'battle:learnMoveNotLearned': + case 'battle:learnMoveForgetQuestion': + case 'battle:learnMovePoof': + case 'battle:learnMoveAnd': + case 'battle:learnMoveForgetSuccess': + this.processInputCommad('accept') + break; + } + + } + break; + case 'AttemptCapturePhase': + let attemptCapturePhase = currentPhase as AttemptCapturePhase; + switch(attemptCapturePhase.attemptCaptureStep) { + case 'battle:pokemonCaught': + this.processInputCommad('accept') + break; + } + break; + + case 'EncounterPhase': + let encounterPhase = currentPhase as EncounterPhase; + switch( encounterPhase.encounterStep) { + case 'battle:encounterMessage': + this.processInputCommad('accept'); + break; + } + break; + + } + } + } + } + + // Process the received message here + } + + private onError(error: Event): void { + console.error("WebSocket error: ", error); + } + + private onClose(event: CloseEvent): void { + console.log("WebSocket connection closed"); } loseFocus(): void { @@ -178,10 +287,11 @@ export class InputsController { this.delLastProcessedMovementTime(buttonUp); } } - +/* setupWebSocketControls(): void { } + */ setupKeyboardControls(): void { const keyCodes = Phaser.Input.Keyboard.KeyCodes; @@ -239,6 +349,7 @@ export class InputsController { } }); } + processInputCommad(input: string): void { const command = input.toLowerCase(); @@ -247,26 +358,26 @@ export class InputsController { 'down' : Button.DOWN, 'left' : Button.LEFT, 'right' : Button.RIGHT, - 'submit' : Button.SUBMIT, + 'accept' : Button.SUBMIT, + 'select' : Button.ACTION, 'cancel' : Button.CANCEL, 'menu' : Button.MENU, 'stats' : Button.STATS - }; - - if (commandMapping[command]) { - this.triggerButtonPress(commandMapping[command]); - } + }; + this.triggerButtonPress(commandMapping[command]); + } + triggerButtonPress(button: Button): void { this.events.emit('input_down', { controller_type: 'command', button: button, }); + this.setLastProcessedMovementTime(button); this.events.emit('input_up', { controller_type: 'command', button: button, }); - this.setLastProcessedMovementTime(button); this.delLastProcessedMovementTime(button); } diff --git a/src/phases.ts b/src/phases.ts index e5d67de28..2f4cd6263 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -646,7 +646,7 @@ export abstract class PartyMemberPokemonPhase extends FieldPhase { } } -export abstract class PlayerPartyMemberPokemonPhase extends PartyMemberPokemonPhase { +export abstract class PlayerPartyMemberPokemonPhase extends PartyMemberPokemonPhase { constructor(scene: BattleScene, partyMemberIndex: integer) { super(scene, partyMemberIndex, true); } @@ -668,6 +668,7 @@ export abstract class EnemyPartyMemberPokemonPhase extends PartyMemberPokemonPha export class EncounterPhase extends BattlePhase { private loaded: boolean; + public encounterStep: string; constructor(scene: BattleScene, loaded?: boolean) { super(scene); @@ -876,6 +877,7 @@ export class EncounterPhase extends BattlePhase { const showDialogueAndSummon = () => { let message: string; this.scene.executeWithSeedOffset(() => message = Utils.randSeedItem(encounterMessages), this.scene.currentBattle.waveIndex); + this.encounterStep = 'battle:encounterMessage'; this.scene.ui.showDialogue(message, trainer.getName(), null, () => { this.scene.charSprite.hide().then(() => this.scene.hideFieldOverlay(250).then(() => doSummon())); }); @@ -2999,6 +3001,10 @@ export class MessagePhase extends Phase { this.scene.ui.showText(this.text, null, () => this.end(), this.callbackDelay || (this.prompt ? 0 : 1500), this.prompt, this.promptDelay); } + getText() { + return this.text; + } + end() { if (this.scene.abilityBar.shown) this.scene.abilityBar.hide(); @@ -3799,7 +3805,8 @@ export class LevelUpPhase extends PlayerPartyMemberPokemonPhase { export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { private moveId: Moves; - + public openMovesRemaining: number; + public learnMovesStep : string; constructor(scene: BattleScene, partyMemberIndex: integer, moveId: Moves) { super(scene, partyMemberIndex); @@ -3813,6 +3820,7 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { const move = allMoves[this.moveId]; const existingMoveIndex = pokemon.getMoveset().findIndex(m => m?.moveId === move.id); + if (existingMoveIndex > -1) return this.end(); @@ -3821,6 +3829,8 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { ? pokemon.getMoveset().length : pokemon.getMoveset().findIndex(m => m === null); + this.openMovesRemaining = emptyMoveIndex+1; + const messageMode = this.scene.ui.getHandler() instanceof EvolutionSceneHandler ? Mode.EVOLUTION_SCENE : Mode.MESSAGE; @@ -3830,6 +3840,7 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { initMoveAnim(this.scene, this.moveId).then(() => { loadMoveAnimAssets(this.scene, [ this.moveId ], true) .then(() => { + this.learnMovesStep = 'battle:learnMove'; this.scene.ui.setMode(messageMode).then(() => { this.scene.playSound('level_up_fanfare'); this.scene.ui.showText(i18next.t('battle:learnMove', { pokemonName: pokemon.name, moveName: move.name }), null, () => { @@ -3841,16 +3852,21 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { }); } else { this.scene.ui.setMode(messageMode).then(() => { + this.learnMovesStep = 'battle:learnMovePrompt'; this.scene.ui.showText(i18next.t('battle:learnMovePrompt', { pokemonName: pokemon.name, moveName: move.name }), null, () => { + this.learnMovesStep = 'battle:learnMoveLimitReached'; this.scene.ui.showText(i18next.t('battle:learnMoveLimitReached', { pokemonName: pokemon.name }), null, () => { + this.learnMovesStep = 'battle:learnMoveReplaceQuestion'; this.scene.ui.showText(i18next.t('battle:learnMoveReplaceQuestion', { moveName: move.name }), null, () => { const noHandler = () => { this.scene.ui.setMode(messageMode).then(() => { + this.learnMovesStep = 'battle:learnMoveStopTeaching'; this.scene.ui.showText(i18next.t('battle:learnMoveStopTeaching', { moveName: move.name }), null, () => { this.scene.ui.setModeWithoutClear(Mode.CONFIRM, () => { this.scene.ui.setMode(messageMode); + this.learnMovesStep = 'battle:learnMoveNotLearned'; this.scene.ui.showText(i18next.t('battle:learnMoveNotLearned', { pokemonName: pokemon.name, moveName: move.name }), null, () => this.end(), null, true); - }, () => { + }, () => { this.scene.ui.setMode(messageMode); this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.partyMemberIndex, this.moveId)); this.end(); @@ -3858,17 +3874,23 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { }); }); }; + this.scene.ui.setModeWithoutClear(Mode.CONFIRM, () => { this.scene.ui.setMode(messageMode); + this.learnMovesStep = 'battle:learnMoveForgetQuestion'; this.scene.ui.showText(i18next.t('battle:learnMoveForgetQuestion'), null, () => { + this.learnMovesStep = 'battle:replaceMove'; this.scene.ui.setModeWithoutClear(Mode.SUMMARY, this.getPokemon(), SummaryUiMode.LEARN_MOVE, move, (moveIndex: integer) => { if (moveIndex === 4) { noHandler(); return; } - this.scene.ui.setMode(messageMode).then(() => { + this.learnMovesStep = 'battle:learnMovePoof'; + this.scene.ui.setMode(messageMode).then(() => { this.scene.ui.showText('@d{32}1, @d{15}2, and@d{15}… @d{15}… @d{15}… @d{15}@s{pb_bounce_1}Poof!', null, () => { + this.learnMovesStep = 'battle:learnMoveForgetSuccess'; this.scene.ui.showText(i18next.t('battle:learnMoveForgetSuccess', { pokemonName: pokemon.name, moveName: pokemon.moveset[moveIndex].getName() }), null, () => { + this.learnMovesStep = 'battle:learnMoveAnd'; this.scene.ui.showText('And…', null, () => { pokemon.setMove(moveIndex, Moves.NONE); this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.partyMemberIndex, this.moveId)); @@ -4008,6 +4030,7 @@ export class AttemptCapturePhase extends PokemonPhase { private pokeballType: PokeballType; private pokeball: Phaser.GameObjects.Sprite; private originalY: number; + public attemptCaptureStep: string; constructor(scene: BattleScene, targetIndex: integer, pokeballType: PokeballType) { super(scene, BattlerIndex.ENEMY + targetIndex); @@ -4185,7 +4208,7 @@ export class AttemptCapturePhase extends PokemonPhase { this.scene.pokemonInfoContainer.show(pokemon, true); this.scene.gameData.updateSpeciesDexIvs(pokemon.species.getRootSpeciesId(true), pokemon.ivs); - + this.attemptCaptureStep = 'battle:pokemonCaught'; this.scene.ui.showText(i18next.t('battle:pokemonCaught', { pokemonName: pokemon.name }), null, () => { const end = () => { this.scene.pokemonInfoContainer.hide(); @@ -4217,6 +4240,7 @@ export class AttemptCapturePhase extends PokemonPhase { Promise.all([ pokemon.hideInfo(), this.scene.gameData.setPokemonCaught(pokemon) ]).then(() => { if (this.scene.getParty().length === 6) { const promptRelease = () => { + this.attemptCaptureStep = 'battle:pokemonCaughtMakeRoom'; this.scene.ui.showText(`Your party is full.\nRelease a Pokémon to make room for ${pokemon.name}?`, null, () => { this.scene.ui.setMode(Mode.CONFIRM, () => { this.scene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, this.fieldIndex, (slotIndex: integer, _option: PartyOption) => { diff --git a/src/ui-inputs.ts b/src/ui-inputs.ts index 38d8e7830..7bcfef19a 100644 --- a/src/ui-inputs.ts +++ b/src/ui-inputs.ts @@ -27,8 +27,13 @@ export class UiInputs { this.listenInputs(); } + processInput(input: string): void { + this.inputsController.processInputCommad(input); + } + listenInputs(): void { this.events.on('input_down', (event) => { + console.log(event); const actions = this.getActionsKeyDown(); if (!actions.hasOwnProperty(event.button)) return; actions[event.button](); diff --git a/vite.config.js b/vite.config.js index 220747b98..8998307c1 100644 --- a/vite.config.js +++ b/vite.config.js @@ -3,7 +3,7 @@ import { defineConfig } from 'vite'; export default defineConfig(({ mode }) => { return { - plugins: [/*fs()*/], + plugins: [], server: { host: '0.0.0.0', port: 8000 }, clearScreen: false, build: {