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