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.
316 lines
7.7 KiB
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 }))
|
|
}
|