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.
215 lines
6.3 KiB
215 lines
6.3 KiB
import {BallState, Player, PlayerPhantom} from "../model/tactic/Player"
|
|
import {middlePos, ratioWithinBase} from "../geo/Pos"
|
|
import {ComponentId, TacticComponent, TacticContent,} from "../model/tactic/Tactic"
|
|
import {overlaps} from "../geo/Box"
|
|
import {Action, ActionKind} from "../model/tactic/Action"
|
|
import {removeBall, updateComponent} from "./TacticContentDomains"
|
|
import {getOrigin} from "./PlayerDomains"
|
|
|
|
export function refreshAllActions(
|
|
actions: Action[],
|
|
components: TacticComponent[],
|
|
) {
|
|
return actions.map((action) => ({
|
|
...action,
|
|
type: getActionKindFrom(action.fromId, action.toId, components),
|
|
}))
|
|
}
|
|
|
|
export function getActionKindFrom(
|
|
originId: ComponentId,
|
|
targetId: ComponentId | null,
|
|
components: TacticComponent[],
|
|
): ActionKind {
|
|
const origin = components.find((p) => p.id == originId)!
|
|
const target = components.find(p => p.id == targetId)
|
|
|
|
let ballState = BallState.NONE
|
|
|
|
if (origin.type == "player" || origin.type == "phantom") {
|
|
ballState = origin.ballState
|
|
}
|
|
|
|
let hasTarget = target ? (target.type != 'phantom' || target.originPlayerId != origin.id) : false
|
|
|
|
return getActionKind(hasTarget, ballState)
|
|
}
|
|
|
|
export function getActionKind(hasTarget: boolean, ballState: BallState): ActionKind {
|
|
switch (ballState) {
|
|
case BallState.HOLDS:
|
|
return hasTarget ? ActionKind.SHOOT : ActionKind.DRIBBLE
|
|
case BallState.SHOOTED:
|
|
return ActionKind.MOVE
|
|
case BallState.NONE:
|
|
return hasTarget ? ActionKind.SCREEN : ActionKind.MOVE
|
|
}
|
|
}
|
|
|
|
export function placeArrow(
|
|
origin: Player | PlayerPhantom,
|
|
courtBounds: DOMRect,
|
|
arrowHead: DOMRect,
|
|
content: TacticContent,
|
|
): { createdAction: Action, newContent: TacticContent } {
|
|
const originRef = document.getElementById(origin.id)!
|
|
const start = ratioWithinBase(
|
|
middlePos(originRef.getBoundingClientRect()),
|
|
courtBounds,
|
|
)
|
|
|
|
/**
|
|
* Creates a new phantom component.
|
|
* Be aware that this function will reassign the `content` parameter.
|
|
* @param receivesBall
|
|
*/
|
|
function createPhantom(receivesBall: boolean): ComponentId {
|
|
const {x, y} = ratioWithinBase(arrowHead, courtBounds)
|
|
|
|
let itemIndex: number
|
|
let originPlayer: Player
|
|
|
|
if (origin.type == "phantom") {
|
|
// if we create a phantom from another phantom,
|
|
// simply add it to the phantom's path
|
|
const originPlr = getOrigin(origin, content.components)!
|
|
itemIndex = originPlr.path!.items.length
|
|
originPlayer = originPlr
|
|
} else {
|
|
// if we create a phantom directly from a player
|
|
// create a new path and add it into
|
|
itemIndex = 0
|
|
originPlayer = origin
|
|
}
|
|
|
|
const path = originPlayer.path
|
|
|
|
const phantomId = "phantom-" + itemIndex + "-" + originPlayer.id
|
|
|
|
content = updateComponent(
|
|
{
|
|
...originPlayer,
|
|
path: {
|
|
items: path ? [...path.items, phantomId] : [phantomId],
|
|
},
|
|
},
|
|
content,
|
|
)
|
|
|
|
const ballState = receivesBall
|
|
? BallState.HOLDS
|
|
: origin.ballState == BallState.HOLDS
|
|
? BallState.HOLDS
|
|
: BallState.NONE
|
|
|
|
const phantom: PlayerPhantom = {
|
|
type: "phantom",
|
|
id: phantomId,
|
|
rightRatio: x,
|
|
bottomRatio: y,
|
|
originPlayerId: originPlayer.id,
|
|
ballState
|
|
}
|
|
content = {
|
|
...content,
|
|
components: [...content.components, phantom],
|
|
}
|
|
return phantom.id
|
|
}
|
|
|
|
for (const component of content.components) {
|
|
if (component.id == origin.id) {
|
|
continue
|
|
}
|
|
|
|
const componentBounds = document
|
|
.getElementById(component.id)!
|
|
.getBoundingClientRect()
|
|
|
|
if (overlaps(componentBounds, arrowHead)) {
|
|
const targetPos = document
|
|
.getElementById(component.id)!
|
|
.getBoundingClientRect()
|
|
|
|
const end = ratioWithinBase(middlePos(targetPos), courtBounds)
|
|
|
|
let toId = component.id
|
|
|
|
if (component.type == "ball") {
|
|
toId = createPhantom(true)
|
|
content = removeBall(content)
|
|
}
|
|
|
|
const action: Action = {
|
|
fromId: originRef.id,
|
|
toId,
|
|
type: getActionKind(true, origin.ballState),
|
|
moveFrom: start,
|
|
segments: [{next: end}],
|
|
}
|
|
|
|
return {
|
|
newContent: {
|
|
...content,
|
|
actions: [...content.actions, action],
|
|
},
|
|
createdAction: action
|
|
}
|
|
}
|
|
}
|
|
|
|
const phantomId = createPhantom(origin.ballState == BallState.HOLDS)
|
|
|
|
const action: Action = {
|
|
fromId: originRef.id,
|
|
toId: phantomId,
|
|
type: getActionKind(false, origin.ballState),
|
|
moveFrom: ratioWithinBase(
|
|
middlePos(originRef.getBoundingClientRect()),
|
|
courtBounds,
|
|
),
|
|
segments: [
|
|
{next: ratioWithinBase(middlePos(arrowHead), courtBounds)},
|
|
],
|
|
}
|
|
return {
|
|
newContent: {
|
|
...content,
|
|
actions: [...content.actions, action],
|
|
},
|
|
createdAction: action
|
|
}
|
|
}
|
|
|
|
export function repositionActionsRelatedTo(
|
|
compId: ComponentId,
|
|
courtBounds: DOMRect,
|
|
actions: Action[],
|
|
): Action[] {
|
|
const posRect = document.getElementById(compId)?.getBoundingClientRect()
|
|
const newPos = posRect != undefined
|
|
? ratioWithinBase(middlePos(posRect), courtBounds)
|
|
: undefined
|
|
|
|
return actions.flatMap((action) => {
|
|
if (newPos == undefined) {
|
|
return []
|
|
}
|
|
|
|
if (action.fromId == compId) {
|
|
return [{...action, moveFrom: newPos}]
|
|
}
|
|
|
|
if (action.toId == compId) {
|
|
const lastIdx = action.segments.length - 1
|
|
const segments = action.segments.toSpliced(lastIdx, 1, {
|
|
...action.segments[lastIdx],
|
|
next: newPos!,
|
|
})
|
|
return [{...action, segments}]
|
|
}
|
|
|
|
return action
|
|
})
|
|
}
|