parent
8d444c38b4
commit
852f163e4a
@ -0,0 +1,214 @@
|
|||||||
|
import {BallState, Player, PlayerPhantom} from "../model/tactic/Player"
|
||||||
|
import {middlePos, ratioWithinBase} from "../geo/Pos"
|
||||||
|
import {ComponentId, TacticComponent, TacticContent,} from "../model/tactic/Tactic"
|
||||||
|
import {overlaps} from "../geo/Box"
|
||||||
|
import {Action, ActionKind} from "../model/tactic/Action"
|
||||||
|
import {removeBall, updateComponent} from "./TacticContentDomains"
|
||||||
|
import {getOrigin} from "./PlayerDomains"
|
||||||
|
|
||||||
|
export function refreshAllActions(
|
||||||
|
actions: Action[],
|
||||||
|
components: TacticComponent[],
|
||||||
|
) {
|
||||||
|
return actions.map((action) => ({
|
||||||
|
...action,
|
||||||
|
type: getActionKindFrom(action.fromId, action.toId, components),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getActionKindFrom(
|
||||||
|
originId: ComponentId,
|
||||||
|
targetId: ComponentId | null,
|
||||||
|
components: TacticComponent[],
|
||||||
|
): ActionKind {
|
||||||
|
const origin = components.find((p) => p.id == originId)!
|
||||||
|
const target = components.find(p => p.id == targetId)
|
||||||
|
|
||||||
|
let ballState = BallState.NONE
|
||||||
|
|
||||||
|
if (origin.type == "player" || origin.type == "phantom") {
|
||||||
|
ballState = origin.ballState
|
||||||
|
}
|
||||||
|
|
||||||
|
let hasTarget = target ? (target.type != 'phantom' || target.originPlayerId != origin.id) : false
|
||||||
|
|
||||||
|
return getActionKind(hasTarget, ballState)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getActionKind(hasTarget: boolean, ballState: BallState): ActionKind {
|
||||||
|
switch (ballState) {
|
||||||
|
case BallState.HOLDS:
|
||||||
|
return hasTarget ? ActionKind.SHOOT : ActionKind.DRIBBLE
|
||||||
|
case BallState.SHOOTED:
|
||||||
|
return ActionKind.MOVE
|
||||||
|
case BallState.NONE:
|
||||||
|
return hasTarget ? ActionKind.SCREEN : ActionKind.MOVE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function placeArrow(
|
||||||
|
origin: Player | PlayerPhantom,
|
||||||
|
courtBounds: DOMRect,
|
||||||
|
arrowHead: DOMRect,
|
||||||
|
content: TacticContent,
|
||||||
|
): { createdAction: Action, newContent: TacticContent } {
|
||||||
|
const originRef = document.getElementById(origin.id)!
|
||||||
|
const start = ratioWithinBase(
|
||||||
|
middlePos(originRef.getBoundingClientRect()),
|
||||||
|
courtBounds,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new phantom component.
|
||||||
|
* Be aware that this function will reassign the `content` parameter.
|
||||||
|
* @param receivesBall
|
||||||
|
*/
|
||||||
|
function createPhantom(receivesBall: boolean): ComponentId {
|
||||||
|
const {x, y} = ratioWithinBase(arrowHead, courtBounds)
|
||||||
|
|
||||||
|
let itemIndex: number
|
||||||
|
let originPlayer: Player
|
||||||
|
|
||||||
|
if (origin.type == "phantom") {
|
||||||
|
// if we create a phantom from another phantom,
|
||||||
|
// simply add it to the phantom's path
|
||||||
|
const originPlr = getOrigin(origin, content.components)!
|
||||||
|
itemIndex = originPlr.path!.items.length
|
||||||
|
originPlayer = originPlr
|
||||||
|
} else {
|
||||||
|
// if we create a phantom directly from a player
|
||||||
|
// create a new path and add it into
|
||||||
|
itemIndex = 0
|
||||||
|
originPlayer = origin
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = originPlayer.path
|
||||||
|
|
||||||
|
const phantomId = "phantom-" + itemIndex + "-" + originPlayer.id
|
||||||
|
|
||||||
|
content = updateComponent(
|
||||||
|
{
|
||||||
|
...originPlayer,
|
||||||
|
path: {
|
||||||
|
items: path ? [...path.items, phantomId] : [phantomId],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
content,
|
||||||
|
)
|
||||||
|
|
||||||
|
const ballState = receivesBall
|
||||||
|
? BallState.HOLDS
|
||||||
|
: origin.ballState == BallState.HOLDS
|
||||||
|
? BallState.HOLDS
|
||||||
|
: BallState.NONE
|
||||||
|
|
||||||
|
const phantom: PlayerPhantom = {
|
||||||
|
type: "phantom",
|
||||||
|
id: phantomId,
|
||||||
|
rightRatio: x,
|
||||||
|
bottomRatio: y,
|
||||||
|
originPlayerId: originPlayer.id,
|
||||||
|
ballState
|
||||||
|
}
|
||||||
|
content = {
|
||||||
|
...content,
|
||||||
|
components: [...content.components, phantom],
|
||||||
|
}
|
||||||
|
return phantom.id
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const component of content.components) {
|
||||||
|
if (component.id == origin.id) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const componentBounds = document
|
||||||
|
.getElementById(component.id)!
|
||||||
|
.getBoundingClientRect()
|
||||||
|
|
||||||
|
if (overlaps(componentBounds, arrowHead)) {
|
||||||
|
const targetPos = document
|
||||||
|
.getElementById(component.id)!
|
||||||
|
.getBoundingClientRect()
|
||||||
|
|
||||||
|
const end = ratioWithinBase(middlePos(targetPos), courtBounds)
|
||||||
|
|
||||||
|
let toId = component.id
|
||||||
|
|
||||||
|
if (component.type == "ball") {
|
||||||
|
toId = createPhantom(true)
|
||||||
|
content = removeBall(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
const action: Action = {
|
||||||
|
fromId: originRef.id,
|
||||||
|
toId,
|
||||||
|
type: getActionKind(true, origin.ballState),
|
||||||
|
moveFrom: start,
|
||||||
|
segments: [{next: end}],
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
newContent: {
|
||||||
|
...content,
|
||||||
|
actions: [...content.actions, action],
|
||||||
|
},
|
||||||
|
createdAction: action
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const phantomId = createPhantom(origin.ballState == BallState.HOLDS)
|
||||||
|
|
||||||
|
const action: Action = {
|
||||||
|
fromId: originRef.id,
|
||||||
|
toId: phantomId,
|
||||||
|
type: getActionKind(false, origin.ballState),
|
||||||
|
moveFrom: ratioWithinBase(
|
||||||
|
middlePos(originRef.getBoundingClientRect()),
|
||||||
|
courtBounds,
|
||||||
|
),
|
||||||
|
segments: [
|
||||||
|
{next: ratioWithinBase(middlePos(arrowHead), courtBounds)},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
newContent: {
|
||||||
|
...content,
|
||||||
|
actions: [...content.actions, action],
|
||||||
|
},
|
||||||
|
createdAction: action
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function repositionActionsRelatedTo(
|
||||||
|
compId: ComponentId,
|
||||||
|
courtBounds: DOMRect,
|
||||||
|
actions: Action[],
|
||||||
|
): Action[] {
|
||||||
|
const posRect = document.getElementById(compId)?.getBoundingClientRect()
|
||||||
|
const newPos = posRect != undefined
|
||||||
|
? ratioWithinBase(middlePos(posRect), courtBounds)
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
return actions.flatMap((action) => {
|
||||||
|
if (newPos == undefined) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.fromId == compId) {
|
||||||
|
return [{...action, moveFrom: newPos}]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.toId == compId) {
|
||||||
|
const lastIdx = action.segments.length - 1
|
||||||
|
const segments = action.segments.toSpliced(lastIdx, 1, {
|
||||||
|
...action.segments[lastIdx],
|
||||||
|
next: newPos!,
|
||||||
|
})
|
||||||
|
return [{...action, segments}]
|
||||||
|
}
|
||||||
|
|
||||||
|
return action
|
||||||
|
})
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
import { Player, PlayerPhantom } from "../model/tactic/Player"
|
||||||
|
import { TacticComponent, TacticContent } from "../model/tactic/Tactic"
|
||||||
|
import { removeComponent, updateComponent } from "./TacticContentDomains"
|
||||||
|
|
||||||
|
export function getOrigin(
|
||||||
|
pathItem: PlayerPhantom,
|
||||||
|
components: TacticComponent[],
|
||||||
|
): Player {
|
||||||
|
// Trust the components to contains only phantoms with valid player origin identifiers
|
||||||
|
return components.find((c) => c.id == pathItem.originPlayerId)! as Player
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removePlayerPath(
|
||||||
|
player: Player,
|
||||||
|
content: TacticContent,
|
||||||
|
): TacticContent {
|
||||||
|
if (player.path == null) {
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const pathElement of player.path.items) {
|
||||||
|
content = removeComponent(pathElement, content)
|
||||||
|
}
|
||||||
|
return updateComponent(
|
||||||
|
{
|
||||||
|
...player,
|
||||||
|
path: null,
|
||||||
|
},
|
||||||
|
content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removePlayer(
|
||||||
|
player: Player | PlayerPhantom,
|
||||||
|
content: TacticContent,
|
||||||
|
): TacticContent {
|
||||||
|
if (player.type == "phantom") {
|
||||||
|
const origin = getOrigin(player, content.components)
|
||||||
|
return truncatePlayerPath(origin, player, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
content = removePlayerPath(player, content)
|
||||||
|
return removeComponent(player.id, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function truncatePlayerPath(
|
||||||
|
player: Player,
|
||||||
|
phantom: PlayerPhantom,
|
||||||
|
content: TacticContent,
|
||||||
|
): TacticContent {
|
||||||
|
if (player.path == null) return content
|
||||||
|
|
||||||
|
const path = player.path!
|
||||||
|
|
||||||
|
let truncateStartIdx = -1
|
||||||
|
|
||||||
|
for (let j = 0; j < path.items.length; j++) {
|
||||||
|
const pathPhantomId = path.items[j]
|
||||||
|
if (truncateStartIdx != -1 || pathPhantomId == phantom.id) {
|
||||||
|
if (truncateStartIdx == -1) truncateStartIdx = j
|
||||||
|
|
||||||
|
//remove the phantom from the tactic
|
||||||
|
content = removeComponent(pathPhantomId, content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return updateComponent(
|
||||||
|
{
|
||||||
|
...player,
|
||||||
|
path:
|
||||||
|
truncateStartIdx == 0
|
||||||
|
? null
|
||||||
|
: {
|
||||||
|
...path,
|
||||||
|
items: path.items.toSpliced(truncateStartIdx),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
content,
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* information about a player that is into a rack
|
||||||
|
*/
|
||||||
|
import { PlayerTeam } from "../model/tactic/Player"
|
||||||
|
|
||||||
|
export interface RackedPlayer {
|
||||||
|
team: PlayerTeam
|
||||||
|
key: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RackedCourtObject = { key: "ball" }
|
@ -0,0 +1,299 @@
|
|||||||
|
import {Pos, ratioWithinBase} from "../geo/Pos"
|
||||||
|
import {BallState, Player, PlayerInfo, PlayerTeam} from "../model/tactic/Player"
|
||||||
|
import {Ball, BALL_ID, BALL_TYPE, CourtObject} from "../model/tactic/CourtObjects"
|
||||||
|
import {ComponentId, TacticComponent, TacticContent,} from "../model/tactic/Tactic"
|
||||||
|
import {overlaps} from "../geo/Box"
|
||||||
|
import {RackedCourtObject, RackedPlayer} from "./RackedItems"
|
||||||
|
import {refreshAllActions} from "./ActionsDomains"
|
||||||
|
import {getOrigin} from "./PlayerDomains";
|
||||||
|
|
||||||
|
export function placePlayerAt(
|
||||||
|
refBounds: DOMRect,
|
||||||
|
courtBounds: DOMRect,
|
||||||
|
element: RackedPlayer,
|
||||||
|
): Player {
|
||||||
|
const {x, y} = ratioWithinBase(refBounds, courtBounds)
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: "player",
|
||||||
|
id: "player-" + element.key + "-" + element.team,
|
||||||
|
team: element.team,
|
||||||
|
role: element.key,
|
||||||
|
rightRatio: x,
|
||||||
|
bottomRatio: y,
|
||||||
|
ballState: BallState.NONE,
|
||||||
|
path: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function placeObjectAt(
|
||||||
|
refBounds: DOMRect,
|
||||||
|
courtBounds: DOMRect,
|
||||||
|
rackedObject: RackedCourtObject,
|
||||||
|
content: TacticContent,
|
||||||
|
): TacticContent {
|
||||||
|
const {x, y} = ratioWithinBase(refBounds, courtBounds)
|
||||||
|
|
||||||
|
let courtObject: CourtObject
|
||||||
|
|
||||||
|
switch (rackedObject.key) {
|
||||||
|
case BALL_TYPE:
|
||||||
|
const playerCollidedIdx = getComponentCollided(
|
||||||
|
refBounds,
|
||||||
|
content.components,
|
||||||
|
BALL_ID,
|
||||||
|
)
|
||||||
|
if (playerCollidedIdx != -1) {
|
||||||
|
return dropBallOnComponent(playerCollidedIdx, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
courtObject = {
|
||||||
|
type: BALL_TYPE,
|
||||||
|
id: BALL_ID,
|
||||||
|
rightRatio: x,
|
||||||
|
bottomRatio: y,
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error("unknown court object " + rackedObject.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...content,
|
||||||
|
components: [...content.components, courtObject],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function dropBallOnComponent(
|
||||||
|
targetedComponentIdx: number,
|
||||||
|
content: TacticContent,
|
||||||
|
): TacticContent {
|
||||||
|
let components = content.components
|
||||||
|
let component = components[targetedComponentIdx]
|
||||||
|
|
||||||
|
let origin
|
||||||
|
let isPhantom: boolean
|
||||||
|
|
||||||
|
if (component.type == 'phantom') {
|
||||||
|
isPhantom = true
|
||||||
|
origin = getOrigin(component, components)
|
||||||
|
} else if (component.type == 'player') {
|
||||||
|
isPhantom = false
|
||||||
|
origin = component
|
||||||
|
} else {
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
|
components = components.toSpliced(targetedComponentIdx, 1, {
|
||||||
|
...component,
|
||||||
|
ballState: BallState.HOLDS,
|
||||||
|
})
|
||||||
|
if (origin.path != null) {
|
||||||
|
const phantoms = origin.path!.items
|
||||||
|
const headingPhantoms = isPhantom ? phantoms.slice(phantoms.indexOf(component.id)) : phantoms
|
||||||
|
components = components.map(c => headingPhantoms.indexOf(c.id) != -1 ? {
|
||||||
|
...c,
|
||||||
|
hasBall: true
|
||||||
|
} : c)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ballObj = components.findIndex((p) => p.type == BALL_TYPE)
|
||||||
|
|
||||||
|
// Maybe the ball is not present on the court as an object component
|
||||||
|
// if so, don't bother removing it from the court.
|
||||||
|
// This can occur if the user drags and drop the ball from a player that already has the ball
|
||||||
|
// to another component
|
||||||
|
if (ballObj != -1) {
|
||||||
|
components.splice(ballObj, 1)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...content,
|
||||||
|
actions: refreshAllActions(content.actions, components),
|
||||||
|
components,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeBall(content: TacticContent): TacticContent {
|
||||||
|
const ballObj = content.components.findIndex((o) => o.type == "ball")
|
||||||
|
|
||||||
|
const components = content.components.map((c) =>
|
||||||
|
(c.type == 'player' || c.type == 'phantom')
|
||||||
|
? {
|
||||||
|
...c,
|
||||||
|
hasBall: false,
|
||||||
|
}
|
||||||
|
: c,
|
||||||
|
)
|
||||||
|
|
||||||
|
// if the ball is already not on the court, do nothing
|
||||||
|
if (ballObj != -1) {
|
||||||
|
components.splice(ballObj, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...content,
|
||||||
|
actions: refreshAllActions(content.actions, components),
|
||||||
|
components,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function placeBallAt(
|
||||||
|
refBounds: DOMRect,
|
||||||
|
courtBounds: DOMRect,
|
||||||
|
content: TacticContent,
|
||||||
|
): {
|
||||||
|
newContent: TacticContent
|
||||||
|
removed: boolean
|
||||||
|
} {
|
||||||
|
if (!overlaps(courtBounds, refBounds)) {
|
||||||
|
return {newContent: removeBall(content), removed: true}
|
||||||
|
}
|
||||||
|
const playerCollidedIdx = getComponentCollided(
|
||||||
|
refBounds,
|
||||||
|
content.components,
|
||||||
|
BALL_ID,
|
||||||
|
)
|
||||||
|
if (playerCollidedIdx != -1) {
|
||||||
|
return {
|
||||||
|
newContent: dropBallOnComponent(playerCollidedIdx, {
|
||||||
|
...content,
|
||||||
|
components: content.components.map((c) =>
|
||||||
|
c.type == "player" || c.type == 'phantom'
|
||||||
|
? {
|
||||||
|
...c,
|
||||||
|
hasBall: false,
|
||||||
|
}
|
||||||
|
: c,
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
removed: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ballIdx = content.components.findIndex((o) => o.type == "ball")
|
||||||
|
|
||||||
|
const {x, y} = ratioWithinBase(refBounds, courtBounds)
|
||||||
|
|
||||||
|
const components = content.components.map((c) =>
|
||||||
|
c.type == "player" || c.type == "phantom"
|
||||||
|
? {
|
||||||
|
...c,
|
||||||
|
hasBall: false,
|
||||||
|
}
|
||||||
|
: c,
|
||||||
|
)
|
||||||
|
|
||||||
|
const ball: Ball = {
|
||||||
|
type: BALL_TYPE,
|
||||||
|
id: BALL_ID,
|
||||||
|
rightRatio: x,
|
||||||
|
bottomRatio: y,
|
||||||
|
}
|
||||||
|
if (ballIdx != -1) {
|
||||||
|
components.splice(ballIdx, 1, ball)
|
||||||
|
} else {
|
||||||
|
components.push(ball)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
newContent: {
|
||||||
|
...content,
|
||||||
|
actions: refreshAllActions(content.actions, components),
|
||||||
|
components,
|
||||||
|
},
|
||||||
|
removed: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function moveComponent(
|
||||||
|
newPos: Pos,
|
||||||
|
component: TacticComponent,
|
||||||
|
info: PlayerInfo,
|
||||||
|
courtBounds: DOMRect,
|
||||||
|
content: TacticContent,
|
||||||
|
removed: (content: TacticContent) => TacticContent,
|
||||||
|
): TacticContent {
|
||||||
|
const playerBounds = document
|
||||||
|
.getElementById(info.id)!
|
||||||
|
.getBoundingClientRect()
|
||||||
|
|
||||||
|
// if the piece is no longer on the court, remove it
|
||||||
|
if (!overlaps(playerBounds, courtBounds)) {
|
||||||
|
return removed(content)
|
||||||
|
}
|
||||||
|
return updateComponent(
|
||||||
|
{
|
||||||
|
...component,
|
||||||
|
rightRatio: newPos.x,
|
||||||
|
bottomRatio: newPos.y,
|
||||||
|
},
|
||||||
|
content,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeComponent(
|
||||||
|
componentId: ComponentId,
|
||||||
|
content: TacticContent,
|
||||||
|
): TacticContent {
|
||||||
|
const componentIdx = content.components.findIndex(
|
||||||
|
(c) => c.id == componentId,
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
...content,
|
||||||
|
components: content.components.toSpliced(componentIdx, 1),
|
||||||
|
actions: content.actions.filter(
|
||||||
|
(a) => a.toId !== componentId && a.fromId !== componentId,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateComponent(
|
||||||
|
component: TacticComponent,
|
||||||
|
content: TacticContent,
|
||||||
|
): TacticContent {
|
||||||
|
const componentIdx = content.components.findIndex(
|
||||||
|
(c) => c.id == component.id,
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
...content,
|
||||||
|
components: content.components.toSpliced(componentIdx, 1, component),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getComponentCollided(
|
||||||
|
bounds: DOMRect,
|
||||||
|
components: TacticComponent[],
|
||||||
|
ignore?: ComponentId,
|
||||||
|
): number | -1 {
|
||||||
|
for (let i = 0; i < components.length; i++) {
|
||||||
|
const component = components[i]
|
||||||
|
|
||||||
|
if (component.id == ignore) continue
|
||||||
|
|
||||||
|
const playerBounds = document
|
||||||
|
.getElementById(component.id)!
|
||||||
|
.getBoundingClientRect()
|
||||||
|
|
||||||
|
if (overlaps(playerBounds, bounds)) {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRackPlayers(
|
||||||
|
team: PlayerTeam,
|
||||||
|
components: TacticComponent[],
|
||||||
|
): RackedPlayer[] {
|
||||||
|
return ["1", "2", "3", "4", "5"]
|
||||||
|
.filter(
|
||||||
|
(role) =>
|
||||||
|
components.findIndex(
|
||||||
|
(c) =>
|
||||||
|
c.type == "player" && c.team == team && c.role == role,
|
||||||
|
) == -1,
|
||||||
|
)
|
||||||
|
.map((key) => ({team, key}))
|
||||||
|
}
|
Loading…
Reference in new issue