Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add buff effects when keystroke velocity reached #13

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion src/extension/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
randomPetType,
randomPetName,
UserPet,
KeyStrokeVelocity,
} from '../panel'

const randomPet = (): UserPet =>
Expand All @@ -33,6 +34,7 @@ class PetPanel {
private _userPet: UserPet
private _extensionUri: vscode.Uri
private _mediaPath: string
private _keystrokeVelocity = new KeyStrokeVelocity()

public static currentPanel: PetPanel | undefined

Expand All @@ -56,19 +58,32 @@ class PetPanel {
this._userPet = userPet
this._extensionUri = extensionUri
this._mediaPath = path.join(extensionPath, 'media')
this._keystrokeVelocity.startWatch()
}

onKeystroke(context: vscode.ExtensionContext) {
this._keystrokeVelocity.addKeyPress()

this._userPet.xp = this._userPet.xp + 1

// Check level change every 10 keystrokes
// Keystroke checks every 10 keystrokes
if (!(this.panel && this._userPet.xp % 10 === 0)) {
return
}

// 💪 Buff
if (this._keystrokeVelocity.isThresholdExceeded()) {
this.panel.webview.postMessage({
command: 'buff-pet',
data: { userPet: this._userPet },
})
this._keystrokeVelocity.reset()
}

const previousLevel = this._userPet.level
mutateLevel({ userPet: this._userPet })

// ⬆ Level up
if (this._userPet.level !== previousLevel) {
this.panel.webview.postMessage({
command: 'update-pet',
Expand Down Expand Up @@ -205,6 +220,9 @@ class PetPanel {
<div id="transition-container">
<img id="transition" nonce="${nonce}" />
</div>
<div id="buff-container">
<img id="buff" nonce="${nonce}" />
</div>
<div id="pet-container" >
<img id="pet" nonce="${nonce}" />
</div>
Expand Down
28 changes: 28 additions & 0 deletions src/panel/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,37 @@ export class DOM {
private _movementContainerSelector: string
private _transitionContainerSelector: string
private _transitionSelector: string
private _buffContainerSelector: string
private _buffSelector: string

private _movementContainerElement: HTMLElement | undefined
private _petImageElement: HTMLImageElement | undefined
private _transitionContainerElement: HTMLElement | undefined
private _transitionImageElement: HTMLImageElement | undefined
private _buffContainerElement: HTMLElement | undefined
private _buffImageElement: HTMLImageElement | undefined

constructor({
movementContainerSelector,
petImageSelector,
transitionContainerSelector,
transitionSelector,
buffContainerSelector,
buffSelector,
}: {
petImageSelector: string
movementContainerSelector: string
transitionContainerSelector: string
transitionSelector: string
buffContainerSelector: string
buffSelector: string
}) {
this._petImageSelector = petImageSelector
this._movementContainerSelector = movementContainerSelector
this._transitionContainerSelector = transitionContainerSelector
this._transitionSelector = transitionSelector
this._buffContainerSelector = buffContainerSelector
this._buffSelector = buffSelector
}

protected getHTMLElement = <T>(elementName: string): T => {
Expand Down Expand Up @@ -70,4 +80,22 @@ export class DOM {
}
return this._transitionImageElement
}

getBuffSelector(): HTMLElement {
if (!this._buffContainerElement) {
this._buffContainerElement = this.getHTMLElement<HTMLElement>(
this._buffContainerSelector
)
}
return this._buffContainerElement
}

getBuffImageSelector(): HTMLImageElement {
if (!this._buffImageElement) {
this._buffImageElement = this.getHTMLElement<HTMLImageElement>(
this._buffSelector
)
}
return this._buffImageElement
}
}
8 changes: 6 additions & 2 deletions src/panel/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ import {
NextFrameFn,
NextFrameFnReturn,
Transforms,
PetAnimation,
Animation,
PetLevel,
State,
ContextTransform,
} from './types'
import { DOM } from './dom'
import { state, initializeState, setState } from './state'
import { KeyStrokeVelocity } from './keystroke-velocity'

export {
petTypes,
Expand All @@ -48,12 +50,14 @@ export {
NextFrameFn,
NextFrameFnReturn,
Transforms,
PetAnimation,
Animation,
PetLevel,
State,
ContextTransform,
initializeState,
state,
setState,
transforms,
DOM,
KeyStrokeVelocity,
}
25 changes: 22 additions & 3 deletions src/panel/xp.ts → src/panel/keystroke-velocity.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export class Xp {
export class KeyStrokeVelocity {
_periods: number
_msPerPeriod: number
_intervalId: NodeJS.Timeout | undefined = undefined
Expand All @@ -7,20 +7,39 @@ export class Xp {
_currentPeriodKeystrokes = 0
_velocity = 0
_multiplier = 0
_threshold = 0

getMultipler() {
return this._multiplier
}

constructor(periods: number = 6, msPerPeriod: number = 10000) {
constructor(
periods: number = 6,
msPerPeriod: number = 10_000,
threshold: number = 5
) {
this._periods = periods
this._msPerPeriod = msPerPeriod
this._threshold = threshold
return this
}

onKeyPress() {
reset() {
this._velocity = 0
this._multiplier = 0
}

addKeyPress() {
if (!this._intervalId) {
return
}
this._currentPeriodKeystrokes++
}

isThresholdExceeded() {
return this._multiplier >= this._threshold
}

startWatch() {
this._intervalId = setInterval(() => {
if (this._periodValues.length >= this._periods) {
Expand Down
94 changes: 80 additions & 14 deletions src/panel/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import {
transforms,
DOM,
state,
PetAnimation,
Animation,
} from './'
import { initializeState } from './state'
import { ContextTransform } from './types'

const defaultState = {
userPet: generatePet({ name: 'unknown', type: 'unknown' }),
Expand All @@ -21,13 +22,19 @@ initializeState(defaultState)
const dom = new DOM({
movementContainerSelector: 'movement-container',
petImageSelector: 'pet',
transitionContainerSelector: 'transition-container',
transitionSelector: 'transition',
transitionContainerSelector: 'transition-container',
buffSelector: 'buff',
buffContainerSelector: 'buff-container',
})

const TICK_INTERVAL_MS = 100
const BUFF_TIMEOUT_MS = 30_000
const BUFF_COUNTDOWN_INTERVAL_MS = 1_000

const tick = ({ userPet }: { userPet: UserPet }) => {
const animations = getPetAnimations({ userPet })

const { leftPosition, direction } = transforms[userPet.state].nextFrame({
containerWidth:
window.innerWidth ||
Expand All @@ -36,9 +43,11 @@ const tick = ({ userPet }: { userPet: UserPet }) => {
leftPosition: userPet.leftPosition,
direction: userPet.direction,
speed: userPet.speed,
offset: getPetAnimations({ userPet }).animation.offset || 0,
offset: animations.pet.offset || 0,
})

const hasChangedDirection = userPet.direction !== direction

userPet.leftPosition = leftPosition
userPet.direction = direction

Expand All @@ -49,12 +58,11 @@ const tick = ({ userPet }: { userPet: UserPet }) => {
const petImageElement = dom.getPetImageSelector()
petImageElement.style.transform = `scaleX(${userPet.direction})`

// ☁ Transition effect
if (userPet.isTransitionIn) {
const { transition: animation } = getPetAnimations({
userPet,
})

const { transition: animation } = animations
if (animation) {
// Fix the transition container in-place
const transitionContainer = dom.getTransitionSelector()
transitionContainer.style.marginLeft = `${userPet.leftPosition}px`

Expand All @@ -66,25 +74,79 @@ const tick = ({ userPet }: { userPet: UserPet }) => {
state.userPet.isTransitionIn = false
}
}

// ✨ Buff effect
const isApplyBuff = !userPet.buffCountdownTimerMs && userPet.isApplyBuff
const isReapplyBuffAnimation =
!userPet.buffCountdownTimerMs && userPet.isApplyBuff

if (isApplyBuff || isReapplyBuffAnimation) {
const { buff: animation } = animations
if (animation) {
setImage({
container: dom.getBuffSelector(),
selector: dom.getBuffImageSelector(),
animation,
userPetContext: userPet,
contextTransforms: animation.contextTransforms,
})
}

userPet.isApplyBuff = false
}
if (isApplyBuff) {
userPet.buffCountdownTimerMs = BUFF_TIMEOUT_MS

const buffIntervalTimeout = setInterval(() => {
console.log(`countdown: ${userPet.buffCountdownTimerMs}`)
userPet.buffCountdownTimerMs -= BUFF_COUNTDOWN_INTERVAL_MS

if (userPet.buffCountdownTimerMs <= 0) {
// Expired ⏰
clearInterval(buffIntervalTimeout)
dom.getBuffImageSelector().style.display = 'none'
}
}, BUFF_COUNTDOWN_INTERVAL_MS)
}
}

const setImage = ({
container,
selector,
animation,
userPetContext,
contextTransforms,
}: {
container: HTMLElement
selector: HTMLImageElement
animation: PetAnimation
animation: Animation
userPetContext?: UserPet
contextTransforms?: ContextTransform[]
}) => {
const { basePetUri } = state

selector.src = `${basePetUri}/${gifs[animation.gif]}`
selector.width = animation.width
selector.style.minWidth = `${animation.width}px`
selector.height = animation.height
let animationWithTransforms = { ...animation }

container.style.left = `${animation.offset ?? 0}px`
if (userPetContext && contextTransforms) {
contextTransforms.map((contextTransform) => {
const transform = contextTransform({ userPetContext })
animationWithTransforms = {
...animationWithTransforms,
...transform,
}
})
}
selector.src = `${basePetUri}/${gifs[animationWithTransforms.gif]}`
selector.style.minWidth = `${animationWithTransforms.width}px`
selector.style.display = 'inline'
selector.width = animationWithTransforms.width || 64
selector.height = animationWithTransforms.height || 64

const positionProp = animation.isFixedPosition ? 'left' : 'marginLeft'

container.style[positionProp] = animationWithTransforms.offset
? `${animationWithTransforms.offset}px`
: 'auto'
}

const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
Expand All @@ -107,10 +169,11 @@ export const addPetToPanel = async ({ userPet }: { userPet: UserPet }) => {
// Give the transition a chance to play
await sleep(TICK_INTERVAL_MS * 2)

const { animation } = getPetAnimations({
const { pet: animation } = getPetAnimations({
userPet,
})

dom.getBuffImageSelector().style.display = 'none'
setImage({
selector: dom.getPetImageSelector(),
animation,
Expand Down Expand Up @@ -148,6 +211,9 @@ export const app = ({
},
})
break
case 'buff-pet':
state.userPet.isApplyBuff = true
break
}
})
}
Loading