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/src/editor/TacticContentDomains.ts

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}))
}