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.
292 lines
7.4 KiB
292 lines
7.4 KiB
import {Pos, ratioWithinBase} from "../geo/Pos"
|
|
import {
|
|
BallState,
|
|
Player,
|
|
PlayerInfo,
|
|
PlayerLike,
|
|
PlayerTeam,
|
|
} from "../model/tactic/Player"
|
|
import {
|
|
Ball,
|
|
BALL_ID,
|
|
BALL_TYPE,
|
|
CourtObject,
|
|
} from "../model/tactic/CourtObjects"
|
|
import {
|
|
ComponentId,
|
|
TacticComponent,
|
|
StepContent,
|
|
} from "../model/tactic/Tactic"
|
|
|
|
import {overlaps} from "../geo/Box"
|
|
import {RackedCourtObject, RackedPlayer} from "./RackedItems"
|
|
import {changePlayerBallState, getComponent, getOrigin} from "./PlayerDomains"
|
|
import {ActionKind} from "../model/tactic/Action.ts"
|
|
|
|
export function placePlayerAt(
|
|
refBounds: DOMRect,
|
|
courtBounds: DOMRect,
|
|
element: RackedPlayer,
|
|
): Player {
|
|
const pos = ratioWithinBase(refBounds, courtBounds)
|
|
|
|
return {
|
|
type: "player",
|
|
id: "player-" + element.key + "-" + element.team,
|
|
team: element.team,
|
|
role: element.key,
|
|
pos,
|
|
ballState: BallState.NONE,
|
|
path: null,
|
|
actions: [],
|
|
}
|
|
}
|
|
|
|
export function placeObjectAt(
|
|
refBounds: DOMRect,
|
|
courtBounds: DOMRect,
|
|
rackedObject: RackedCourtObject,
|
|
content: StepContent,
|
|
): StepContent {
|
|
const pos = 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, true)
|
|
}
|
|
|
|
courtObject = {
|
|
type: BALL_TYPE,
|
|
id: BALL_ID,
|
|
pos,
|
|
actions: [],
|
|
}
|
|
break
|
|
}
|
|
default:
|
|
throw new Error("unknown court object " + rackedObject.key)
|
|
}
|
|
|
|
return {
|
|
...content,
|
|
components: [...content.components, courtObject],
|
|
}
|
|
}
|
|
|
|
export function dropBallOnComponent(
|
|
targetedComponentIdx: number,
|
|
content: StepContent,
|
|
setAsOrigin: boolean,
|
|
): StepContent {
|
|
const component = content.components[targetedComponentIdx]
|
|
|
|
if (component.type === "player" || component.type === "phantom") {
|
|
const newState =
|
|
setAsOrigin ||
|
|
component.ballState === BallState.PASSED_ORIGIN ||
|
|
component.ballState === BallState.HOLDS_ORIGIN
|
|
? BallState.HOLDS_ORIGIN
|
|
: BallState.HOLDS_BY_PASS
|
|
|
|
content = changePlayerBallState(component, newState, content)
|
|
}
|
|
|
|
return removeBall(content)
|
|
}
|
|
|
|
export function removeBall(content: StepContent): StepContent {
|
|
const ballObjIdx = content.components.findIndex((o) => o.type == "ball")
|
|
|
|
if (ballObjIdx == -1) {
|
|
return content
|
|
}
|
|
|
|
return {
|
|
...content,
|
|
components: content.components.toSpliced(ballObjIdx, 1),
|
|
}
|
|
}
|
|
|
|
export function placeBallAt(
|
|
refBounds: DOMRect,
|
|
courtBounds: DOMRect,
|
|
content: StepContent,
|
|
): StepContent {
|
|
if (!overlaps(courtBounds, refBounds)) {
|
|
return removeBall(content)
|
|
}
|
|
const playerCollidedIdx = getComponentCollided(
|
|
refBounds,
|
|
content.components,
|
|
BALL_ID,
|
|
)
|
|
|
|
if (playerCollidedIdx != -1) {
|
|
return dropBallOnComponent(playerCollidedIdx, content, true)
|
|
}
|
|
|
|
const ballIdx = content.components.findIndex((o) => o.type == "ball")
|
|
|
|
const pos = ratioWithinBase(refBounds, courtBounds)
|
|
|
|
const ball: Ball = {
|
|
type: BALL_TYPE,
|
|
id: BALL_ID,
|
|
pos,
|
|
actions: [],
|
|
}
|
|
|
|
let components = content.components
|
|
|
|
if (ballIdx != -1) {
|
|
components = components.toSpliced(ballIdx, 1, ball)
|
|
} else {
|
|
components = components.concat(ball)
|
|
}
|
|
|
|
return {
|
|
...content,
|
|
components,
|
|
}
|
|
}
|
|
|
|
export function moveComponent(
|
|
newPos: Pos,
|
|
component: TacticComponent,
|
|
info: PlayerInfo,
|
|
courtBounds: DOMRect,
|
|
content: StepContent,
|
|
removed: (content: StepContent) => StepContent,
|
|
): StepContent {
|
|
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)
|
|
}
|
|
|
|
const isPhantom = component.type === "phantom"
|
|
|
|
if (isPhantom && component.pos.type === "follows") {
|
|
const referent = component.pos.attach
|
|
const origin = getOrigin(component, content.components)
|
|
const originPathItems = origin.path!.items
|
|
const phantomIdx = originPathItems.indexOf(component.id)
|
|
|
|
const playerBeforePhantom: PlayerLike =
|
|
phantomIdx == 0
|
|
? origin
|
|
: getComponent(
|
|
originPathItems[phantomIdx - 1],
|
|
content.components,
|
|
)
|
|
// detach the action from the screen target and transform it to a regular move action to the phantom.
|
|
content = updateComponent(
|
|
{
|
|
...playerBeforePhantom,
|
|
actions: playerBeforePhantom.actions.map((a) =>
|
|
a.target === referent
|
|
? {
|
|
...a,
|
|
segments: a.segments.toSpliced(
|
|
a.segments.length - 2,
|
|
1,
|
|
{
|
|
...a.segments[a.segments.length - 1],
|
|
next: component.id,
|
|
},
|
|
),
|
|
target: component.id,
|
|
type: ActionKind.MOVE,
|
|
}
|
|
: a,
|
|
),
|
|
},
|
|
content,
|
|
)
|
|
}
|
|
|
|
content = updateComponent(
|
|
<TacticComponent>{
|
|
...component,
|
|
pos: isPhantom
|
|
? {
|
|
type: "fixed",
|
|
...newPos,
|
|
}
|
|
: newPos,
|
|
},
|
|
content,
|
|
)
|
|
return content
|
|
}
|
|
|
|
export function removeComponent(
|
|
componentId: ComponentId,
|
|
content: StepContent,
|
|
): StepContent {
|
|
return {
|
|
...content,
|
|
components: content.components.filter((c) => c.id !== componentId),
|
|
}
|
|
}
|
|
|
|
export function updateComponent(
|
|
component: TacticComponent,
|
|
content: StepContent,
|
|
): StepContent {
|
|
return {
|
|
...content,
|
|
components: content.components.map((c) =>
|
|
c.id === component.id ? component : c,
|
|
),
|
|
}
|
|
}
|
|
|
|
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}))
|
|
}
|