diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..68bf4f3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +dist/ +.idea/ +core-build/ +cache/ +.DS_Store \ No newline at end of file diff --git a/controller/InGameState.ts b/controller/InGameState.ts new file mode 100644 index 0000000..54f5e87 --- /dev/null +++ b/controller/InGameState.ts @@ -0,0 +1,296 @@ +import type { PluginContext } from 'league-prod-toolkit/core/modules/Module' +import { AllGameData, Player, Event } from '../types/AllGameData' +import { Config } from '../types/Config' +import { ItemEpicness } from '../types/Items' +import { InGameState as InGameStateType } from '../types/InGameState' + +export class InGameState { + + public gameState : InGameStateType + public gameData : any[] = [] + public itemEpicness : number[] + + public actions : Array<(allGameData : AllGameData, i: number) => void> = [] + + constructor ( + private namespace: string, + private ctx: PluginContext, + private config: Config, + private statics: any + ) { + this.itemEpicness = this.config.items.map(i => ItemEpicness[i]) + + this.gameState = { + towers : { + 100 : { + L : {}, + C : {}, + R : {} + }, + 200 : { + L : {}, + C : {}, + R : {} + }, + }, + showInhibitors : null, + inhibitors : { + 100 : { + L1 : { + alive : true, + respawnAt : 0, + respawnIn : 0, + percent : 0 + }, + C1 : { + alive : true, + respawnAt : 0, + respawnIn : 0, + percent : 0 + }, + R1 : { + alive : true, + respawnAt : 0, + respawnIn : 0, + percent : 0 + } + }, + 200 : { + L1 : { + alive : true, + respawnAt : 0, + respawnIn : 0, + percent : 0 + }, + C1 : { + alive : true, + respawnAt : 0, + respawnIn : 0, + percent : 0 + }, + R1 : { + alive : true, + respawnAt : 0, + respawnIn : 0, + percent : 0 + } + }, + }, + player : { + 0 : { + level : 0, + items : new Set(), + }, + 1 : { + level : 0, + items : new Set(), + }, + 2 : { + level : 0, + items : new Set(), + }, + 3 : { + level : 0, + items : new Set(), + }, + 4 : { + level : 0, + items : new Set(), + }, + 5 : { + level : 0, + items : new Set(), + }, + 6 : { + level : 0, + items : new Set(), + }, + 7 : { + level : 0, + items : new Set(), + }, + 8 : { + level : 0, + items : new Set(), + }, + 9 : { + level : 0, + items : new Set(), + } + } + } + + this.ctx.LPTE.emit({ + meta: { + namespace: this.namespace, + type: 'update', + version: 1 + }, + state : this.gameState + }) + } + + public handelData (allGameData: AllGameData) : void { + if (this.gameData.length > 0) { + const previousGameData = this.gameData[this.gameData.length -1] + this.checkPlayerUpdate(allGameData) + this.checkEventUpdate(allGameData, previousGameData) + + this.actions.forEach((func, i) => { + func(allGameData, i) + }) + } + + this.gameData.push(allGameData) + } + + private checkPlayerUpdate (allGameData: AllGameData) { + if (this.config.items.length === 0) return + if (allGameData.allPlayers.length === 0) return + + allGameData.allPlayers.forEach((player, i) => { + this.checkItemUpdate(player, i) + this.checkLevelUpdate(player, i) + }) + } + + private checkLevelUpdate (currentPlayerState: Player, id: number) { + if (currentPlayerState.level === this.gameState.player[id].level) return + if (!this.config.level.includes(currentPlayerState.level.toString())) return + + this.gameState.player[id].level = currentPlayerState.level + + this.ctx.LPTE.emit({ + meta: { + type: 'level-update', + namespace: this.namespace, + version: 1 + }, + team: currentPlayerState.team === "ORDER" ? 100 : 200, + player: id, + level: currentPlayerState.level + }) + } + + private checkItemUpdate (currentPlayerState: Player, id: number) { + const previousItems = this.gameState.player[id].items + + for (const item of currentPlayerState.items) { + + const itemID = item.itemID + if (previousItems.has(itemID)) continue + + const itemBinFind = this.statics.itemBin.find((i: any) => i.itemID === itemID) + if (itemBinFind === undefined) continue + + if (!this.itemEpicness.includes(itemBinFind.epicness)) continue + + this.gameState.player[id].items.add(itemID) + + this.ctx.LPTE.emit({ + meta: { + type: 'item-update', + namespace: this.namespace, + version: 1 + }, + team: currentPlayerState.team === "ORDER" ? 100 : 200, + player: id, + item: itemID + }) + } + } + + // --- + + private checkEventUpdate (allGameData: AllGameData, previousGameData: AllGameData) { + if (allGameData.events.Events.length === 0 || previousGameData.events.Events.length === 0) return + + const newEvents = allGameData.events.Events.slice(previousGameData.events.Events.length) + + newEvents.forEach(event => { + if (event.EventName === "InhibKilled") { + this.handleInhibEvent(event) + } else if (event.EventName === "TurretKilled") { + this.handleTowerEvent(event) + } + }) + } + + private handleInhibEvent (event: Event) { + console.log(event) + const split = event.InhibKilled.split('_') as string[] + const team = split[1] === 'T1' ? 100 : 200 + const lane = split[2] as 'L1' | 'C1' | 'R1' + const respawnAt = Math.round(event.EventTime) + (60 * 5) + + console.log(this.gameState.inhibitors[team][lane].alive) + + if (!this.gameState.inhibitors[team][lane].alive) return + + this.gameState.inhibitors[team][lane] = { + alive : false, + respawnAt : respawnAt, + respawnIn : (60 * 5), + percent : 100 + } + + this.actions.push((allGameData, i) => { + const gameState = allGameData.gameData + const diff = respawnAt - Math.round(gameState.gameTime) + const percent = Math.round((diff * 100) / (60 * 5)) + + this.gameState.inhibitors[team][lane] = { + alive : false, + respawnAt : respawnAt, + respawnIn : diff, + percent : 100 + } + + this.ctx.LPTE.emit({ + meta: { + namespace: this.namespace, + type: 'inhib-update', + version: 1 + }, + team, + lane, + percent, + respawnIn: diff + }) + + if (diff <= 0) { + this.gameState.inhibitors[team][lane] = { + alive : true, + respawnAt : 0, + respawnIn : 0, + percent : 0 + } + this.actions.splice(i, 1) + } + }) + } + + private handleTowerEvent (event: Event) { + console.log(event) + const split = event.TurretKilled.split('_') as string[] + const team = split[1] === 'T1' ? 100 : 200 + const lane = split[2] as 'L' | 'C' | 'R' + const turret = split[3] + + console.log(this.gameState.towers[team][lane][turret]) + if (this.gameState.towers[team][lane][turret] === false) return + + this.gameState.towers[team][lane][turret] = false + + this.ctx.LPTE.emit({ + meta: { + namespace: this.namespace, + type: 'tower-update', + version: 1 + }, + team, + lane, + turret + }) + } +} \ No newline at end of file diff --git a/frontend/frontend.js b/frontend/frontend.js new file mode 100644 index 0000000..f6ad304 --- /dev/null +++ b/frontend/frontend.js @@ -0,0 +1,56 @@ +$('#ingame-embed').val(`${location.href}/gfx/ingmae.html`); + +const namespace = 'league-in-game'; + +$('#settings').on('submit', (e) => { + e.preventDefault() + + LPTE.emit({ + meta: { + namespace, + type: 'set-settings', + version: 1 + }, + items: $('#items').val(), + level: $('#level').val() + }) +}) + +function showInhibs (side) { + LPTE.emit({ + meta: { + namespace, + type: 'show-inhibs', + version: 1 + }, + side + }) +} + +function hideInhibs () { + LPTE.emit({ + meta: { + namespace, + type: 'hide-inhibs', + version: 1 + } + }) +} + +function initSettings (settings) { + $('#items').val(settings.items) + $('#level').val(settings.level) +} + +LPTE.onready(async () => { + const settings = await LPTE.request({ + meta: { + namespace, + type: 'get-settings', + version: 1 + } + }) + initSettings(settings) + + LPTE.on(namespace, 'set-settings', initSettings) +}) \ No newline at end of file diff --git a/frontend/gfx/img/bottom.svg b/frontend/gfx/img/bottom.svg new file mode 100644 index 0000000..de927fc --- /dev/null +++ b/frontend/gfx/img/bottom.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/gfx/img/inhibbadge.png b/frontend/gfx/img/inhibbadge.png new file mode 100644 index 0000000..6b16805 Binary files /dev/null and b/frontend/gfx/img/inhibbadge.png differ diff --git a/frontend/gfx/img/mid.svg b/frontend/gfx/img/mid.svg new file mode 100644 index 0000000..bb144da --- /dev/null +++ b/frontend/gfx/img/mid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/gfx/img/top.svg b/frontend/gfx/img/top.svg new file mode 100644 index 0000000..ee5cf7c --- /dev/null +++ b/frontend/gfx/img/top.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/gfx/img/turret.png b/frontend/gfx/img/turret.png new file mode 100644 index 0000000..74c636d Binary files /dev/null and b/frontend/gfx/img/turret.png differ diff --git a/frontend/gfx/ingame.css b/frontend/gfx/ingame.css new file mode 100644 index 0000000..e346aaa --- /dev/null +++ b/frontend/gfx/ingame.css @@ -0,0 +1,246 @@ +*, *::before, *::after { + padding: 0; + margin: 0; + box-sizing: border-box; +} + +body { + width: 1920px; + height: 1080px; + overflow: hidden; + display: flex; + background-size: contain; + background-repeat: no-repeat; + margin-top: 154px; + font-family: var(--secondary-font-family); + font-weight: 700; + position: relative; +} + +.player { + width: 74px; + height: 71px; + overflow: hidden; + position: relative; + transform-origin: center bottom; + display: flex; + justify-content: center; + align-items: center; + transform: scaleY(0); +} + +.level { + display: none; + transform-origin: center; + font-size: 50px; + transform: translateY(-100%) +} + +.levelUp { + animation: backgroundUp 0.4s ease-out forwards, + backgroundDown 0.4s ease-out 2.85s forwards; +} + +.levelUp .level { + display: block; + animation: levelIn 0.4s ease-in 0.05s forwards, + levelOut 0.4s ease-in 2.6s forwards; +} + +.item { + display: none; + object-fit: contain; + width: 60px; + height: 60px; + transform: translateY(-100%) +} + +.itemBuy { + animation: backgroundUp 0.4s ease-out forwards, + backgroundDown 0.4s ease-out 3.45s forwards; +} + +.itemBuy .item { + display: block; + animation: levelIn 0.4s ease-in 0.1s forwards, + levelOut 0.4s ease-in 3.15s forwards; +} + +.player:not(:last-child) { + margin-bottom: 32px; +} + +#blue { + margin-left: 2px; +} + +#blue .player { + background: var(--blue-team); + background: linear-gradient(180deg, var(--blue-team) 0%, var(--blue-team-dark) 250%); + -webkit-mask-image: radial-gradient(circle 18px at 70px 9px, transparent 0, transparent 12px, black 13px); +} + +#blue .player .level { + color: var(--background-color); +} + +#red { + margin-right: 2px; + margin-left: auto; +} + +#red .player { + background: var(--red-team); + background: linear-gradient(180deg, var(--red-team) 0%, var(--red-team-dark) 250%); + -webkit-mask-image: radial-gradient(circle 18px at 4px 9px, transparent 0, transparent 12px, black 13px); +} + +#red .player .level { + color: var(--text-color); +} + +@keyframes backgroundUp { + from { + transform: scaleY(0); + } + to { + transform: scaleY(1); + } +} + +@keyframes backgroundDown { + from { + transform: scaleY(1); + } + to { + transform: scaleY(0); + } +} + +@keyframes levelIn { + from { + transform: translateY(-100%); + } + to { + transform: translateY(0); + } +} + +@keyframes levelOut { + from { + transform: translateY(0); + } + to { + transform: translateY(-100%); + } +} + +#inhibDiv { + opacity: 1; + background-image: url(./img/inhibbadge.png); + background-size: contain; + background-repeat: no-repeat; + width: 310px; + height: 135px; + position: absolute; + left: 0; + bottom: 135px; + transform: translateY(-100%); + transition: opacity 0.3s ease; + color: var(--text-color) +} + +.inhibitors { + transform: scale(1); + width: 100%; + display: flex; + flex-wrap: wrap; + justify-content: space-around; + opacity: 1; + transition: opacity 0.5s ease; + padding: 7px 30px 10px 22px; + position: absolute; +} + +.inhibitors h3 { + text-align: center; + width: 100%; + margin-bottom: 10px; + font-family: var(--primary-font-family); + font-size: 30px; + position: relative; + letter-spacing: 7.5px; +} + +.inhibitor, +.inhibitor svg { + width: 50px; + height: 50px; +} + +.inhibitor { + --percent: 0%; + position: relative; + text-align: center; +} + +.inhibitor.L1::before { + content: url(./img/top.svg); +} +.inhibitor.C1::before { + content: url(./img/mid.svg); +} +.inhibitor.R1::before { + content: url(./img/bottom.svg); +} + +.inhibitor::before { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + z-index: 10; + -webkit-mask-image: linear-gradient(to bottom, transparent 0, transparent calc(var(--percent) * 1%), black calc(calc(var(--percent) * 1%) - 1px)); +} + +#blueSide h3 { + color: var(--blue-team); +} + +#redSide h3 { + color: var(--red-team); +} + +.hide { + opacity: 0 !important; +} + +#turrets { + position: absolute; + top: -140px; + left: 50%; + transform: translateX(-50%); + width: 610px; + font-size: 20px; + display: flex; + justify-content: space-between; +} + +.turrets { + display: flex; +} + +.turret { + margin-right: 5px; + object-fit: contain; + width: 18px; +} + +#blueTurrets { + color: #0099e0; +} + +#redTurrets { + color: #e83d3d; +} \ No newline at end of file diff --git a/frontend/gfx/ingame.js b/frontend/gfx/ingame.js new file mode 100644 index 0000000..11e1e83 --- /dev/null +++ b/frontend/gfx/ingame.js @@ -0,0 +1,165 @@ +const namespace = 'league-in-game'; +const blueTeam = document.querySelector('#blue') +const redTeam = document.querySelector('#red') + +function getPlayerId(id) { + if (id > 4) return id - 5 + else return id +} + +function levelUpdate (e) { + const playerId = getPlayerId(e.player) + + const team = e.team === 100 ? blueTeam : redTeam + const playerDiv = team.children[playerId] + + const levelContainer = playerDiv.querySelector('.level') + + if (playerDiv.classList.contains('levelUp') || playerDiv.classList.contains('itemBuy')) { + return setTimeout(() => { + levelUpdate(e) + }, 3000) + } + + levelContainer.innerHTML = e.level + playerDiv.classList.add('levelUp') + setTimeout(() => { + playerDiv.classList.remove('levelUp') + }, 6000) +} + +function itemUpdate (e) { + const playerId = getPlayerId(e.player) + + const team = e.team === 100 ? blueTeam : redTeam + const playerDiv = team.children[playerId] + + const levelContainer = playerDiv.querySelector('.item') + + if (playerDiv.classList.contains('levelUp') || playerDiv.classList.contains('itemBuy')) { + return setTimeout(() => { + itemUpdate(e) + }, 3000) + } + + levelContainer.src = `/serve/static-league/img/item/${e.item}.png` + playerDiv.classList.add('itemBuy') + setTimeout(() => { + playerDiv.classList.remove('itemBuy') + }, 6000) +} + +const inhibDiv = document.querySelector('#inhibDiv') +const blueSide = inhibDiv.querySelector('#blueSide') +const redSide = inhibDiv.querySelector('#redSide') + +function inhibUpdate (e) { + const team = e.team === 100 ? blueSide : redSide + const inhib = team.querySelector(`.${e.lane}`) + inhib.style.setProperty('--percent', e.percent) + inhib.querySelector('p').innerText = convertSecsToTime(e.respawnIn) +} + +const turretDiv = document.querySelector('#turrets') +const blueTurrets = turretDiv.querySelector('#blueTurrets') +const redTurrets = turretDiv.querySelector('#redTurrets') + +function towerUpdate (e) { + const team = e.team === '100' ? redTurrets : blueTurrets + const value = team.querySelector('.value') + const newValue = (Number(value.innerText) || 0) + 1 + value.innerText = newValue +} + +function setGameState (e) { + const state = e.state + + for (const [teamId, team] of Object.entries(state.towers)) { + for (const lane of Object.values(team)) { + const teamDiv = teamId === '100' ? redTurrets : blueTurrets + const value = teamDiv.querySelector('.value') + let newValue = 0 + + for (const alive of Object.values(lane)) { + if (alive) continue + + newValue += 1 + value.textContent = newValue + 1 + } + + value.textContent = (Number(value.innerText) || 0) + } + } + + for (const [teamId, team] of Object.entries(state.inhibitors)) { + for (const [lane, data] of Object.entries(team)) { + const teamDiv = teamId === 100 ? blueSide : redSide + const div = teamDiv.querySelector(`.${lane}`) + + if (data.alive) { + div.style.setProperty('--percent', '0') + div.querySelector('p').innerText = convertSecsToTime(0) + } else { + div.style.setProperty('--percent', data.percent) + div.querySelector('p').innerText = convertSecsToTime(data.respawnIn) + } + } + } + + if (state.showInhibitors !== null) { + inhibDiv.classList.remove('hide') + if (state.showInhibitors === 100) { + blueSide.classList.remove('hide') + redSide.classList.add('hide') + } else { + blueSide.classList.add('hide') + redSide.classList.remove('hide') + } + } else { + inhibDiv.classList.add('hide') + blueSide.classList.add('hide') + redSide.classList.add('hide') + } +} + +function convertSecsToTime (secs) { + const minutes = Math.floor(secs / 60); + const seconds = secs - minutes * 60; + return `${('0' + minutes).slice(-2)}:${('0' + seconds).slice(-2)}` +} + +LPTE.onready(async () => { + LPTE.on(namespace, 'level-update', levelUpdate) + LPTE.on(namespace, 'item-update', itemUpdate) + LPTE.on(namespace, 'inhib-update', inhibUpdate) + LPTE.on(namespace, 'tower-update', towerUpdate) + LPTE.on(namespace, 'update', setGameState) + + LPTE.on(namespace, 'show-inhibs', (e) => { + inhibDiv.classList.remove('hide') + + if (e.side === 100) { + blueSide.classList.remove('hide') + redSide.classList.add('hide') + } else { + blueSide.classList.add('hide') + redSide.classList.remove('hide') + } + }); + + LPTE.on(namespace, 'hide-inhibs', () => { + inhibDiv.classList.add('hide') + blueSide.classList.add('hide') + redSide.classList.add('hide') + }); + + const res = await LPTE.request({ + meta: { + namespace, + type: 'request', + version: 1 + } + }); + + setGameState(res) +}) \ No newline at end of file diff --git a/frontend/gfx/ingmae.html b/frontend/gfx/ingmae.html new file mode 100644 index 0000000..00b5552 --- /dev/null +++ b/frontend/gfx/ingmae.html @@ -0,0 +1,104 @@ + + +
+ + + +