Skip to content

Commit

Permalink
New keybiding for emotes: maintain Ctrl to show emote menu, Ctrl+1..9…
Browse files Browse the repository at this point in the history
… to trigger emote 1..9 (#1646)
  • Loading branch information
sylvainpolletvillard authored Apr 12, 2024
1 parent 17317db commit 1845bd2
Show file tree
Hide file tree
Showing 16 changed files with 207 additions and 122 deletions.
1 change: 1 addition & 0 deletions app/public/dist/client/changelog/patch-4.10.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,4 @@
- New title: Stargazer - Get Solgaleo or Lunala
- Changed Scribble "Rare is Expensive": Buying XP now costs 8 instead of 4. Units are bought and sold for 1 gold less.
- New Scribble rule: Free Market
- New keybiding for emotes: maintain Ctrl to show emote menu, Ctrl+1..9 to trigger emote 1..9
1 change: 1 addition & 0 deletions app/public/dist/client/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -2558,6 +2558,7 @@
"key_desc_refresh": "Refresh shop",
"key_desc_avatar_anim": "Play avatar animation",
"key_desc_avatar_emotes": "Toggle emote menu",
"key_desc_avatar_show_emote": "Trigger emote",
"jukebox": "Jukebox",
"gadgets": "Gadgets",
"gadgets_unlocked": "gadgets unlocked",
Expand Down
1 change: 1 addition & 0 deletions app/public/dist/client/locales/fr/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -2507,6 +2507,7 @@
"key_desc_refresh": "Relancer la boutique",
"key_desc_avatar_anim": "Jouer l'emote de l'avatar",
"key_desc_avatar_emotes": "Voir le menu des emotes",
"key_desc_avatar_show_emote": "Lancer l'emote",
"jukebox": "Jukebox",
"gadgets": "Gadgets",
"gadgets_unlocked": "gadgets débloqués",
Expand Down
2 changes: 1 addition & 1 deletion app/public/src/game/components/board-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,7 @@ export default class BoardManager {
return benchSize
}

toggleAnimation(playerId: string, emote?: string) {
showEmote(playerId: string, emote?: string) {
const player =
this.playerAvatar.playerId === playerId
? this.playerAvatar
Expand Down
12 changes: 12 additions & 0 deletions app/public/src/game/components/emote-menu.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
.emote-menu li {
display: block;
list-style: none;
position: relative;
}

.emote-menu li img {
Expand All @@ -20,6 +21,17 @@
box-shadow: 2px 2px #00000060;
}

.emote-menu li .counter {
position: absolute;
bottom: 0;
left: 0.25em;
opacity: 0.5;
color: black;
font-size: 1em;
text-shadow: -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff,
1px 1px 0 #fff;
}

.emote-menu li img.locked {
filter: grayscale(1) contrast(0.5);
}
Expand Down
49 changes: 19 additions & 30 deletions app/public/src/game/components/emote-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,21 @@ import ReactDOM from "react-dom/client"
import { useTranslation } from "react-i18next"
import { PRECOMPUTED_EMOTIONS_PER_POKEMON_INDEX } from "../../../../models/precomputed"
import { IPlayer } from "../../../../types"
import { Emotion } from "../../../../types/enum/Emotion"
import { throttle } from "../../../../utils/function"
import { AvatarEmotions, Emotion } from "../../../../types/enum/Emotion"
import { logger } from "../../../../utils/logger"
import { cc } from "../../pages/utils/jsx"
import store from "../../stores"
import { toggleAnimation } from "../../stores/NetworkStore"
import { getAvatarString, getPortraitSrc } from "../../utils"
import { getPortraitSrc } from "../../utils"
import "./emote-menu.css"

const sendEmote = throttle(function (
index: string,
shiny: boolean,
emotion: Emotion
) {
store.dispatch(toggleAnimation(getAvatarString(index, shiny, emotion)))
},
3000)

export function EmoteMenuComponent(props: {
player: IPlayer
index: string
shiny: boolean
sendEmote: (emotion: Emotion) => void
}) {
const { t } = useTranslation()
const emotions: Emotion[] = [
Emotion.HAPPY,
Emotion.JOYOUS,
Emotion.DETERMINED,
Emotion.PAIN,
Emotion.ANGRY,
Emotion.CRYING,
Emotion.SURPRISED,
Emotion.STUNNED,
Emotion.DIZZY
].filter((emotion) => {
const emotions: Emotion[] = AvatarEmotions.filter((emotion) => {
const indexEmotion = Object.values(Emotion).indexOf(emotion)
return (
PRECOMPUTED_EMOTIONS_PER_POKEMON_INDEX[props.index]?.[indexEmotion] === 1
Expand All @@ -52,18 +32,17 @@ export function EmoteMenuComponent(props: {
<div>{t("no_emotions_available")}</div>
) : (
<ul>
{emotions.map((emotion) => {
{emotions.map((emotion, i) => {
const unlocked = pConfig && pConfig.emotions.includes(emotion)
return (
<li key={emotion}>
<img
src={getPortraitSrc(props.index, props.shiny, emotion)}
title={emotion + (!unlocked ? " (locked)" : "")}
className={cc({ locked: !unlocked })}
onClick={() =>
unlocked && sendEmote(props.index, props.shiny, emotion)
}
onClick={() => unlocked && props.sendEmote(emotion)}
/>
<span className="counter">{i + 1}</span>
</li>
)
})}
Expand All @@ -73,7 +52,12 @@ export function EmoteMenuComponent(props: {

export default class EmoteMenu extends GameObjects.DOMElement {
dom: HTMLDivElement
constructor(scene: Phaser.Scene, avatarIndex: string, shiny: boolean) {
constructor(
scene: Phaser.Scene,
avatarIndex: string,
shiny: boolean,
sendEmote: (emotion: Emotion) => void
) {
super(scene, -350, -150)
const state = store.getState()
const player = state.game.players.find(
Expand All @@ -85,7 +69,12 @@ export default class EmoteMenu extends GameObjects.DOMElement {
const root = ReactDOM.createRoot(this.dom)
if (player) {
root.render(
<EmoteMenuComponent player={player} index={avatarIndex} shiny={shiny} />
<EmoteMenuComponent
player={player}
index={avatarIndex}
shiny={shiny}
sendEmote={sendEmote}
/>
)
} else {
logger.error(`Cant' find player bound to EmoteMenu`)
Expand Down
20 changes: 17 additions & 3 deletions app/public/src/game/components/minigame-manager.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { t } from "i18next"
import {
Emotion,
IFloatingItem,
IPokemonAvatar,
IPortal,
ISynergySymbol
} from "../../../../types"
import { PokemonActionState } from "../../../../types/enum/Game"
import { Pkm } from "../../../../types/enum/Pokemon"
import { SpecialGameRule } from "../../../../types/enum/SpecialGameRule"
import { logger } from "../../../../utils/logger"
Expand All @@ -16,13 +18,12 @@ import {
import AnimationManager from "../animation-manager"
import GameScene from "../scenes/game-scene"
import { FloatingItem } from "./floating-item"
import PokemonSprite from "./pokemon"
import PokemonAvatar from "./pokemon-avatar"
import PokemonSpecial from "./pokemon-special"
import { Portal, SynergySymbol } from "./portal"

export default class MinigameManager {
pokemons: Map<string, PokemonSprite>
pokemons: Map<string, PokemonAvatar>
items: Map<string, FloatingItem>
portals: Map<string, Portal>
symbols: Map<string, SynergySymbol>
Expand All @@ -39,7 +40,7 @@ export default class MinigameManager {
avatars: Map<string, IPokemonAvatar>,
items: Map<string, IFloatingItem>
) {
this.pokemons = new Map<string, PokemonSprite>()
this.pokemons = new Map<string, PokemonAvatar>()
this.items = new Map<string, FloatingItem>()
this.portals = new Map<string, Portal>()
this.symbols = new Map<string, SynergySymbol>()
Expand Down Expand Up @@ -357,4 +358,17 @@ export default class MinigameManager {
)
}
}

showEmote(id: string, emote: Emotion) {
const pokemonAvatar = this.pokemons.get(id)
if (pokemonAvatar) {
pokemonAvatar.action = PokemonActionState.EMOTE
this.animationManager.animatePokemon(
pokemonAvatar,
PokemonActionState.EMOTE,
false
)
pokemonAvatar.drawSpeechBubble(emote, false)
}
}
}
101 changes: 84 additions & 17 deletions app/public/src/game/components/pokemon-avatar.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { GameObjects } from "phaser"
import PokemonFactory from "../../../../models/pokemon-factory"
import { IPokemonAvatar } from "../../../../types"
import { AvatarEmotions, Emotion, IPokemonAvatar } from "../../../../types"
import { GamePhaseState } from "../../../../types/enum/Game"
import { playSound, SOUNDS } from "../../pages/utils/audio"
import store from "../../stores"
import { toggleAnimation } from "../../stores/NetworkStore"
import { getAvatarSrc } from "../../utils"
import { showEmote } from "../../stores/NetworkStore"
import { getAvatarSrc, getAvatarString } from "../../utils"
import GameScene from "../scenes/game-scene"
import EmoteMenu from "./emote-menu"
import LifeBar from "./life-bar"
import PokemonSprite from "./pokemon"
import { throttle } from "../../../../utils/function"

export default class PokemonAvatar extends PokemonSprite {
circleHitbox: GameObjects.Ellipse | undefined
Expand Down Expand Up @@ -47,6 +48,7 @@ export default class PokemonAvatar extends PokemonSprite {
this.drawLifebar()
}
this.registerKeys()
this.sendEmote = throttle(this.sendEmote, 1000).bind(this)
}

registerKeys() {
Expand All @@ -55,17 +57,52 @@ export default class PokemonAvatar extends PokemonSprite {
this.playAnimation()
}
})
this.scene.input.keyboard!.on("keydown-S", () => {
const scene = this.scene as GameScene
if (
this.isCurrentPlayerAvatar &&
this.scene &&
scene.room?.state.phase !== GamePhaseState.MINIGAME &&
this.scene.game
) {
this.toggleEmoteMenu()
this.scene.input.keyboard!.on("keydown-CTRL", () => {
if (this.isCurrentPlayerAvatar && this.scene?.game) {
this.showEmoteMenu()
}
})

this.scene.input.keyboard!.on("keyup-CTRL", () => {
this.hideEmoteMenu()
})

const NUM_KEYS = [
"ONE",
"TWO",
"THREE",
"FOUR",
"FIVE",
"SIX",
"SEVEN",
"EIGHT",
"NINE"
]
NUM_KEYS.forEach((keycode, i) => {
const onKeydown = (event) => {
console.log("onkeydown", event, {
cpa: this.isCurrentPlayerAvatar,
sg: this.scene?.game,
ctrl: event.ctrlKey
})
if (this.isCurrentPlayerAvatar && this.scene?.game && event.ctrlKey) {
this.sendEmote(AvatarEmotions[i])
}
}
this.scene.input.keyboard!.on("keydown-" + keycode, onKeydown)
this.scene.input.keyboard!.on("keydown-NUMPAD_" + keycode, onKeydown)
})

// do not forget to clean up parent listeners after destroy
this.sprite.once("destroy", () => {
this.scene.input.keyboard!.off("keydown-A")
this.scene.input.keyboard!.off("keydown-CTRL")
this.scene.input.keyboard!.off("keyup-CTRL")
NUM_KEYS.forEach((keycode) => {
this.scene.input.keyboard!.off("keydown-" + keycode)
this.scene.input.keyboard!.off("keydown-NUMPAD_" + keycode)
})
})
}

drawCircles() {
Expand Down Expand Up @@ -146,19 +183,49 @@ export default class PokemonAvatar extends PokemonSprite {
this.add(this.lifebar)
}

toggleEmoteMenu() {
showEmoteMenu() {
if (this.isCurrentPlayerAvatar && !this.emoteMenu) {
this.emoteMenu = new EmoteMenu(
this.scene,
this.index,
this.shiny,
this.sendEmote
)
this.add(this.emoteMenu)
}
}

hideEmoteMenu() {
if (this.emoteMenu) {
this.emoteMenu.destroy()
this.emoteMenu = null
} else if (this.isCurrentPlayerAvatar) {
this.emoteMenu = new EmoteMenu(this.scene, this.index, this.shiny)
this.add(this.emoteMenu)
}
}

toggleEmoteMenu() {
if (this.emoteMenu) this.hideEmoteMenu()
else this.showEmoteMenu()
}

sendEmote(emotion: Emotion) {
const state = store.getState()
const player = state.game.players.find(
(p) => p.id === state.game.currentPlayerId
)
const pokemonCollection = player?.pokemonCollection
const pConfig = pokemonCollection?.[this.index]
const unlocked = pConfig && pConfig.emotions.includes(emotion)
if (unlocked) {
store.dispatch(
showEmote(getAvatarString(this.index, this.shiny, emotion))
)
this.hideEmoteMenu()
}
}

playAnimation() {
try {
store.dispatch(toggleAnimation())
store.dispatch(showEmote())
} catch (err) {
console.error("could not play animation", err)
}
Expand Down
26 changes: 9 additions & 17 deletions app/public/src/pages/component/keybind-info/keybind-info.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,19 @@
align-items: center;
justify-content: center;
flex-direction: column;
font-size: 1.5rem;
width: 400px;
padding: 1.5rem 2rem;
color: white;
}

.keybind-table {
width: 100%;
.keybind-container dl {
display: grid;
grid-template-columns: max-content auto;
gap: 0.5em;
}

.keybind-table-header {
border-bottom: 4px solid #4f5160;
.keybind-container dt {
grid-column-start: 1;
text-align: right;
}

.keybind-table th:first-child,
.keybind-table td:first-child {
text-align: center;
border-right: 4px solid #4f5160;
}

.keybind-table th:last-child,
.keybind-table td:last-child {
padding-left: 12px;
.keybind-container dd {
grid-column-start: 2;
}
Loading

0 comments on commit 1845bd2

Please sign in to comment.