You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Application-Web/front/editor/TacticContentDomains.ts

316 lines
7.7 KiB

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 { 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,
actions: [],
}
}
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,
actions: [],
}
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,
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,
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,
actions: [],
}
if (ballIdx != -1) {
components.splice(ballIdx, 1, ball)
} else {
components.push(ball)
}
return {
newContent: {
...content,
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),
}
}
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 }))
}