parent
6df90bb3aa
commit
b9bd18c331
@ -1,243 +0,0 @@
|
|||||||
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 { changePlayerBallState } 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, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
setAsOrigin: boolean,
|
|
||||||
): TacticContent {
|
|
||||||
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: TacticContent): TacticContent {
|
|
||||||
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: TacticContent,
|
|
||||||
): TacticContent {
|
|
||||||
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 { x, y } = ratioWithinBase(refBounds, courtBounds)
|
|
||||||
|
|
||||||
const ball: Ball = {
|
|
||||||
type: BALL_TYPE,
|
|
||||||
id: BALL_ID,
|
|
||||||
rightRatio: x,
|
|
||||||
bottomRatio: y,
|
|
||||||
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: 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 {
|
|
||||||
return {
|
|
||||||
...content,
|
|
||||||
components: content.components.filter((c) => c.id !== componentId),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateComponent(
|
|
||||||
component: TacticComponent,
|
|
||||||
content: TacticContent,
|
|
||||||
): TacticContent {
|
|
||||||
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 }))
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
import { Player, PlayerPhantom } from "./Player"
|
|
||||||
import { Action } from "./Action"
|
|
||||||
import { CourtObject } from "./CourtObjects"
|
|
||||||
|
|
||||||
export interface Tactic {
|
|
||||||
id: number
|
|
||||||
name: string
|
|
||||||
content: TacticContent
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TacticContent {
|
|
||||||
components: TacticComponent[]
|
|
||||||
//actions: Action[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TacticComponent = Player | CourtObject | PlayerPhantom
|
|
||||||
export type ComponentId = string
|
|
||||||
|
|
||||||
export interface Component<T> {
|
|
||||||
/**
|
|
||||||
* The component's type
|
|
||||||
*/
|
|
||||||
readonly type: T
|
|
||||||
/**
|
|
||||||
* The component's identifier
|
|
||||||
*/
|
|
||||||
readonly id: ComponentId
|
|
||||||
/**
|
|
||||||
* Percentage of the component's position to the bottom (0 means top, 1 means bottom, 0.5 means middle)
|
|
||||||
*/
|
|
||||||
readonly bottomRatio: number
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Percentage of the component's position to the right (0 means left, 1 means right, 0.5 means middle)
|
|
||||||
*/
|
|
||||||
readonly rightRatio: number
|
|
||||||
|
|
||||||
readonly actions: Action[]
|
|
||||||
}
|
|
@ -1,87 +0,0 @@
|
|||||||
.step-piece {
|
|
||||||
position: relative;
|
|
||||||
font-family: monospace;
|
|
||||||
pointer-events: all;
|
|
||||||
|
|
||||||
background-color: var(--editor-tree-step-piece);
|
|
||||||
color: var(--selected-team-secondarycolor);
|
|
||||||
|
|
||||||
border-radius: 100px;
|
|
||||||
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
user-select: none;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
border: 2px solid var(--editor-tree-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-piece-selected {
|
|
||||||
border: 2px solid var(--selection-color-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-piece-selected,
|
|
||||||
.step-piece:focus,
|
|
||||||
.step-piece:hover {
|
|
||||||
background-color: var(--editor-tree-step-piece-hovered);
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-piece-actions {
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
column-gap: 5px;
|
|
||||||
top: -140%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-piece-selected .step-piece-actions {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-icon,
|
|
||||||
.remove-icon {
|
|
||||||
background-color: white;
|
|
||||||
border-radius: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-icon {
|
|
||||||
fill: var(--add-icon-fill);
|
|
||||||
}
|
|
||||||
|
|
||||||
.remove-icon {
|
|
||||||
fill: var(--remove-icon-fill);
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-children {
|
|
||||||
margin-top: 10vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-group {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.steps-tree {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
padding-top: 10%;
|
|
||||||
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
Loading…
Reference in new issue