|
|
@ -1,10 +1,30 @@
|
|
|
|
import {BallState, Player, PlayerLike, PlayerPhantom,} from "../model/tactic/Player"
|
|
|
|
import {
|
|
|
|
import {ComponentId, TacticComponent, TacticContent} from "../model/tactic/Tactic"
|
|
|
|
BallState,
|
|
|
|
|
|
|
|
Player,
|
|
|
|
|
|
|
|
PlayerLike,
|
|
|
|
|
|
|
|
PlayerPhantom,
|
|
|
|
|
|
|
|
} from "../model/tactic/Player"
|
|
|
|
|
|
|
|
import {
|
|
|
|
|
|
|
|
ComponentId,
|
|
|
|
|
|
|
|
TacticComponent,
|
|
|
|
|
|
|
|
TacticContent,
|
|
|
|
|
|
|
|
} from "../model/tactic/Tactic"
|
|
|
|
import { removeComponent, updateComponent } from "./TacticContentDomains"
|
|
|
|
import { removeComponent, updateComponent } from "./TacticContentDomains"
|
|
|
|
import {removeAllActionsTargeting, spreadNewStateFromOriginStateChange,} from "./ActionsDomains"
|
|
|
|
import {
|
|
|
|
|
|
|
|
removeAllActionsTargeting,
|
|
|
|
|
|
|
|
spreadNewStateFromOriginStateChange,
|
|
|
|
|
|
|
|
} from "./ActionsDomains"
|
|
|
|
import { ActionKind } from "../model/tactic/Action"
|
|
|
|
import { ActionKind } from "../model/tactic/Action"
|
|
|
|
import {add, minus, norm, Pos, posWithinBase, ratioWithinBase, relativeTo} from "../geo/Pos.ts"
|
|
|
|
import {
|
|
|
|
import {PLAYER_RADIUS_PIXELS} from "../components/editor/CourtPlayer.tsx";
|
|
|
|
add,
|
|
|
|
|
|
|
|
minus,
|
|
|
|
|
|
|
|
norm,
|
|
|
|
|
|
|
|
Pos,
|
|
|
|
|
|
|
|
posWithinBase,
|
|
|
|
|
|
|
|
ratioWithinBase,
|
|
|
|
|
|
|
|
relativeTo,
|
|
|
|
|
|
|
|
} from "../geo/Pos.ts"
|
|
|
|
|
|
|
|
import { PLAYER_RADIUS_PIXELS } from "../components/editor/CourtPlayer.tsx"
|
|
|
|
|
|
|
|
|
|
|
|
export function getOrigin(
|
|
|
|
export function getOrigin(
|
|
|
|
pathItem: PlayerPhantom,
|
|
|
|
pathItem: PlayerPhantom,
|
|
|
@ -14,8 +34,13 @@ export function getOrigin(
|
|
|
|
return components.find((c) => c.id == pathItem.originPlayerId)! as Player
|
|
|
|
return components.find((c) => c.id == pathItem.originPlayerId)! as Player
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function getPlayerNextTo(player: PlayerLike, n: number, components: TacticComponent[]): PlayerLike | undefined {
|
|
|
|
export function getPlayerNextTo(
|
|
|
|
const playerOrigin = player.type === "player" ? player : getOrigin(player, components)
|
|
|
|
player: PlayerLike,
|
|
|
|
|
|
|
|
n: number,
|
|
|
|
|
|
|
|
components: TacticComponent[],
|
|
|
|
|
|
|
|
): PlayerLike | undefined {
|
|
|
|
|
|
|
|
const playerOrigin =
|
|
|
|
|
|
|
|
player.type === "player" ? player : getOrigin(player, components)
|
|
|
|
const pathItems = playerOrigin.path?.items!
|
|
|
|
const pathItems = playerOrigin.path?.items!
|
|
|
|
|
|
|
|
|
|
|
|
// add one as there is a shifting because a Player is never at the head of its own path
|
|
|
|
// add one as there is a shifting because a Player is never at the head of its own path
|
|
|
@ -24,20 +49,24 @@ export function getPlayerNextTo(player: PlayerLike, n: number, components: Tacti
|
|
|
|
const targetIdx = idx + n
|
|
|
|
const targetIdx = idx + n
|
|
|
|
|
|
|
|
|
|
|
|
// remove the screen phantom
|
|
|
|
// remove the screen phantom
|
|
|
|
const result = targetIdx == 0 ? playerOrigin : getComponent<PlayerLike>(pathItems[targetIdx - 1], components)
|
|
|
|
const result =
|
|
|
|
|
|
|
|
targetIdx == 0
|
|
|
|
|
|
|
|
? playerOrigin
|
|
|
|
|
|
|
|
: getComponent<PlayerLike>(pathItems[targetIdx - 1], components)
|
|
|
|
return result
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//FIXME this function can be a bottleneck if the phantom's position is
|
|
|
|
//FIXME this function can be a bottleneck if the phantom's position is
|
|
|
|
// following another phantom and / or the origin of the phantom is another
|
|
|
|
// following another phantom and / or the origin of the phantom is another
|
|
|
|
export function computePhantomPositioning(phantom: PlayerPhantom,
|
|
|
|
export function computePhantomPositioning(
|
|
|
|
|
|
|
|
phantom: PlayerPhantom,
|
|
|
|
content: TacticContent,
|
|
|
|
content: TacticContent,
|
|
|
|
area: DOMRect): Pos {
|
|
|
|
area: DOMRect,
|
|
|
|
|
|
|
|
): Pos {
|
|
|
|
const positioning = phantom.pos
|
|
|
|
const positioning = phantom.pos
|
|
|
|
|
|
|
|
|
|
|
|
// If the position is already known and fixed, return the pos
|
|
|
|
// If the position is already known and fixed, return the pos
|
|
|
|
if (positioning.type === "fixed")
|
|
|
|
if (positioning.type === "fixed") return positioning
|
|
|
|
return positioning
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// If the position is to determine (positioning.type = "follows"), determine the phantom's pos
|
|
|
|
// If the position is to determine (positioning.type = "follows"), determine the phantom's pos
|
|
|
|
// by calculating it from the referent position, and the action that targets the referent.
|
|
|
|
// by calculating it from the referent position, and the action that targets the referent.
|
|
|
@ -46,7 +75,8 @@ export function computePhantomPositioning(phantom: PlayerPhantom,
|
|
|
|
|
|
|
|
|
|
|
|
// Get the referent from the components
|
|
|
|
// Get the referent from the components
|
|
|
|
const referent: PlayerLike = getComponent(positioning.attach, components)
|
|
|
|
const referent: PlayerLike = getComponent(positioning.attach, components)
|
|
|
|
const referentPos = referent.type === "player"
|
|
|
|
const referentPos =
|
|
|
|
|
|
|
|
referent.type === "player"
|
|
|
|
? referent.pos
|
|
|
|
? referent.pos
|
|
|
|
: computePhantomPositioning(referent, content, area)
|
|
|
|
: computePhantomPositioning(referent, content, area)
|
|
|
|
|
|
|
|
|
|
|
@ -55,35 +85,46 @@ export function computePhantomPositioning(phantom: PlayerPhantom,
|
|
|
|
const originPathItems = origin.path!.items
|
|
|
|
const originPathItems = origin.path!.items
|
|
|
|
const phantomIdx = originPathItems.indexOf(phantom.id)
|
|
|
|
const phantomIdx = originPathItems.indexOf(phantom.id)
|
|
|
|
|
|
|
|
|
|
|
|
const playerBeforePhantom: PlayerLike = phantomIdx == 0 ? origin : getComponent(originPathItems[phantomIdx - 1], components)
|
|
|
|
const playerBeforePhantom: PlayerLike =
|
|
|
|
const action = playerBeforePhantom.actions.find(a => a.target === positioning.attach)!
|
|
|
|
phantomIdx == 0
|
|
|
|
|
|
|
|
? origin
|
|
|
|
|
|
|
|
: getComponent(originPathItems[phantomIdx - 1], components)
|
|
|
|
|
|
|
|
const action = playerBeforePhantom.actions.find(
|
|
|
|
|
|
|
|
(a) => a.target === positioning.attach,
|
|
|
|
|
|
|
|
)!
|
|
|
|
|
|
|
|
|
|
|
|
const segments = action.segments
|
|
|
|
const segments = action.segments
|
|
|
|
const lastSegment = segments[segments.length - 1]
|
|
|
|
const lastSegment = segments[segments.length - 1]
|
|
|
|
const lastSegmentStart = segments[segments.length - 2]?.next
|
|
|
|
const lastSegmentStart = segments[segments.length - 2]?.next
|
|
|
|
const pivotPoint = lastSegment.controlPoint ?? (lastSegmentStart
|
|
|
|
const pivotPoint =
|
|
|
|
|
|
|
|
lastSegment.controlPoint ??
|
|
|
|
|
|
|
|
(lastSegmentStart
|
|
|
|
? typeof lastSegmentStart === "string"
|
|
|
|
? typeof lastSegmentStart === "string"
|
|
|
|
? document.getElementById(lastSegmentStart)!.getBoundingClientRect()
|
|
|
|
? document
|
|
|
|
|
|
|
|
.getElementById(lastSegmentStart)!
|
|
|
|
|
|
|
|
.getBoundingClientRect()
|
|
|
|
: lastSegmentStart
|
|
|
|
: lastSegmentStart
|
|
|
|
: playerBeforePhantom.type === "phantom"
|
|
|
|
: playerBeforePhantom.type === "phantom"
|
|
|
|
? computePhantomPositioning(playerBeforePhantom, content, area)
|
|
|
|
? computePhantomPositioning(playerBeforePhantom, content, area)
|
|
|
|
: playerBeforePhantom.pos)
|
|
|
|
: playerBeforePhantom.pos)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const segment = posWithinBase(relativeTo(referentPos, pivotPoint), area)
|
|
|
|
const segment = posWithinBase(relativeTo(referentPos, pivotPoint), area)
|
|
|
|
const segmentLength = norm(segment)
|
|
|
|
const segmentLength = norm(segment)
|
|
|
|
const phantomDistanceFromReferent = PLAYER_RADIUS_PIXELS //TODO Place this in constants
|
|
|
|
const phantomDistanceFromReferent = PLAYER_RADIUS_PIXELS //TODO Place this in constants
|
|
|
|
const segmentProjection = minus(area, {
|
|
|
|
const segmentProjection = minus(area, {
|
|
|
|
x: (segment.x / segmentLength) * phantomDistanceFromReferent,
|
|
|
|
x: (segment.x / segmentLength) * phantomDistanceFromReferent,
|
|
|
|
y: (segment.y / segmentLength) * phantomDistanceFromReferent
|
|
|
|
y: (segment.y / segmentLength) * phantomDistanceFromReferent,
|
|
|
|
})
|
|
|
|
})
|
|
|
|
const segmentProjectionRatio: Pos = ratioWithinBase(segmentProjection, area)
|
|
|
|
const segmentProjectionRatio: Pos = ratioWithinBase(segmentProjection, area)
|
|
|
|
|
|
|
|
|
|
|
|
return add(referentPos, segmentProjectionRatio)
|
|
|
|
return add(referentPos, segmentProjectionRatio)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function getComponent<T extends TacticComponent>(id: string, components: TacticComponent[]): T {
|
|
|
|
export function getComponent<T extends TacticComponent>(
|
|
|
|
return components.find(c => c.id === id)! as T
|
|
|
|
id: string,
|
|
|
|
|
|
|
|
components: TacticComponent[],
|
|
|
|
|
|
|
|
): T {
|
|
|
|
|
|
|
|
return components.find((c) => c.id === id)! as T
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function areInSamePath(a: PlayerLike, b: PlayerLike) {
|
|
|
|
export function areInSamePath(a: PlayerLike, b: PlayerLike) {
|
|
|
@ -141,13 +182,18 @@ export function clearPlayerPath(
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function removeAllPhantomsAttached(to: ComponentId, content: TacticContent): TacticContent {
|
|
|
|
function removeAllPhantomsAttached(
|
|
|
|
|
|
|
|
to: ComponentId,
|
|
|
|
|
|
|
|
content: TacticContent,
|
|
|
|
|
|
|
|
): TacticContent {
|
|
|
|
let i = 0
|
|
|
|
let i = 0
|
|
|
|
while (i < content.components.length) {
|
|
|
|
while (i < content.components.length) {
|
|
|
|
const component = content.components[i]
|
|
|
|
const component = content.components[i]
|
|
|
|
if (component.type === "phantom") {
|
|
|
|
if (component.type === "phantom") {
|
|
|
|
|
|
|
|
if (
|
|
|
|
if (component.pos.type === "follows" && component.pos.attach === to) {
|
|
|
|
component.pos.type === "follows" &&
|
|
|
|
|
|
|
|
component.pos.attach === to
|
|
|
|
|
|
|
|
) {
|
|
|
|
content = removePlayer(component, content)
|
|
|
|
content = removePlayer(component, content)
|
|
|
|
continue
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -165,16 +211,24 @@ export function removePlayer(
|
|
|
|
content = removeAllPhantomsAttached(player.id, content)
|
|
|
|
content = removeAllPhantomsAttached(player.id, content)
|
|
|
|
|
|
|
|
|
|
|
|
if (player.type === "phantom") {
|
|
|
|
if (player.type === "phantom") {
|
|
|
|
|
|
|
|
|
|
|
|
const pos = player.pos
|
|
|
|
const pos = player.pos
|
|
|
|
// if the phantom was attached to another player, remove the action that symbolizes the attachment
|
|
|
|
// if the phantom was attached to another player, remove the action that symbolizes the attachment
|
|
|
|
if (pos.type === "follows") {
|
|
|
|
if (pos.type === "follows") {
|
|
|
|
const playerBefore = getPlayerNextTo(player, -1, content.components)!
|
|
|
|
const playerBefore = getPlayerNextTo(
|
|
|
|
const actionIdx = playerBefore.actions.findIndex(a => a.target === pos.attach)
|
|
|
|
player,
|
|
|
|
content = updateComponent({
|
|
|
|
-1,
|
|
|
|
|
|
|
|
content.components,
|
|
|
|
|
|
|
|
)!
|
|
|
|
|
|
|
|
const actionIdx = playerBefore.actions.findIndex(
|
|
|
|
|
|
|
|
(a) => a.target === pos.attach,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
content = updateComponent(
|
|
|
|
|
|
|
|
{
|
|
|
|
...playerBefore,
|
|
|
|
...playerBefore,
|
|
|
|
actions: playerBefore.actions.toSpliced(actionIdx, 1)
|
|
|
|
actions: playerBefore.actions.toSpliced(actionIdx, 1),
|
|
|
|
}, content)
|
|
|
|
},
|
|
|
|
|
|
|
|
content,
|
|
|
|
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const origin = getOrigin(player, content.components)
|
|
|
|
const origin = getOrigin(player, content.components)
|
|
|
|