diff --git a/src/api/wired/WiredActionLayoutCode.ts b/src/api/wired/WiredActionLayoutCode.ts index 14ecc94..1aa75e2 100644 --- a/src/api/wired/WiredActionLayoutCode.ts +++ b/src/api/wired/WiredActionLayoutCode.ts @@ -30,4 +30,5 @@ export class WiredActionLayoutCode public static FURNI_NEIGHBORHOOD_SELECTOR: number = 29; public static FURNI_BYTYPE_SELECTOR: number = 30; public static USERS_AREA_SELECTOR: number = 31; + public static USERS_NEIGHBORHOOD_SELECTOR: number = 32; } diff --git a/src/components/wired/views/actions/WiredActionLayoutView.tsx b/src/components/wired/views/actions/WiredActionLayoutView.tsx index cbd1ad3..44c2632 100644 --- a/src/components/wired/views/actions/WiredActionLayoutView.tsx +++ b/src/components/wired/views/actions/WiredActionLayoutView.tsx @@ -4,6 +4,7 @@ import { WiredActionFurniAreaView } from '../selectors/WiredActionFurniAreaView' import { WiredSelectorFurniNeighborhoodView } from '../selectors/WiredSelectorFurniNeighborhoodView'; import { WiredSelectorFurniByTypeView } from '../selectors/WiredSelectorFurniByTypeView'; import { WiredSelectorUsersAreaView } from '../selectors/WiredSelectorUsersAreaView'; +import { WiredSelectorUsersNeighborhoodView } from '../selectors/WiredSelectorUsersNeighborhoodView'; import { WiredActionBotFollowAvatarView } from './WiredActionBotFollowAvatarView'; import { WiredActionBotGiveHandItemView } from './WiredActionBotGiveHandItemView'; import { WiredActionBotMoveView } from './WiredActionBotMoveView'; @@ -91,6 +92,8 @@ export const WiredActionLayoutView = (code: number) => return ; case WiredActionLayoutCode.USERS_AREA_SELECTOR: return ; + case WiredActionLayoutCode.USERS_NEIGHBORHOOD_SELECTOR: + return ; } return null; diff --git a/src/components/wired/views/selectors/WiredSelectorUsersNeighborhoodView.tsx b/src/components/wired/views/selectors/WiredSelectorUsersNeighborhoodView.tsx new file mode 100644 index 0000000..a69046f --- /dev/null +++ b/src/components/wired/views/selectors/WiredSelectorUsersNeighborhoodView.tsx @@ -0,0 +1,296 @@ +import { CSSProperties, FC, useCallback, useEffect, useState } from 'react'; +import { FaMinus, FaPlus, FaTimes } from 'react-icons/fa'; +import { MdGridOn } from 'react-icons/md'; +import { LocalizeText } from '../../../../api'; +import { Button, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredActionBaseView } from '../actions/WiredActionBaseView'; + +const SOURCE_USER_TRIGGER = 0; +const SOURCE_USER_SIGNAL = 1; +const SOURCE_USER_CLICKED = 2; + +const USER_SOURCES = [ + { value: SOURCE_USER_TRIGGER, label: 'wiredfurni.params.sources.users.0' }, + { value: SOURCE_USER_SIGNAL, label: 'wiredfurni.params.sources.users.201' }, + { value: SOURCE_USER_CLICKED, label: 'wiredfurni.params.sources.users.11' }, +]; + +const TILE_W = 22; +const TILE_H = 11; +const GRID_RANGE = 4; +const CX = GRID_RANGE * TILE_W + TILE_W / 2; +const CY = GRID_RANGE * TILE_H + TILE_H / 2; +const GRID_PX_W = (GRID_RANGE * 2 + 1) * TILE_W; +const GRID_PX_H = (GRID_RANGE * 2 + 1) * TILE_H; + +type Tile = { x: number; y: number }; + +const tileIncluded = (tiles: Tile[], x: number, y: number) => + tiles.some(t => t.x === x && t.y === y); + +const tileLeft = (rx: number, ry: number) => + CX + (rx - ry) * (TILE_W / 2) - TILE_W / 2; + +const tileTop = (rx: number, ry: number) => + CY + (rx + ry) * (TILE_H / 2) - TILE_H / 2; + +interface GridProps { + selectedTiles: Tile[]; + invert: boolean; + onToggle: (x: number, y: number) => void; +} + +const NeighborhoodGrid: FC = ({ selectedTiles, invert, onToggle }) => +{ + const tiles: JSX.Element[] = []; + + for (let ry = -GRID_RANGE; ry <= GRID_RANGE; ry++) + { + for (let rx = -GRID_RANGE; rx <= GRID_RANGE; rx++) + { + const isCenter = rx === 0 && ry === 0; + const isSelected = tileIncluded(selectedTiles, rx, ry); + const isActive = invert ? !isSelected : isSelected; + const left = tileLeft(rx, ry); + const top_ = tileTop(rx, ry); + const zIdx = rx + ry + GRID_RANGE * 2 + 10; + + const bgColor = isCenter + ? '#ff9500' + : isActive + ? '#3399ff' + : '#2a3042'; + + const borderColor = isCenter + ? '#cc6600' + : isActive + ? '#1166cc' + : '#1a2032'; + + const diamond: CSSProperties = { + position: 'absolute', + width: TILE_W, + height: TILE_H, + left, + top: top_, + clipPath: 'polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)', + backgroundColor: bgColor, + cursor: isCenter ? 'default' : 'pointer', + zIndex: zIdx, + }; + + const border: CSSProperties = { + position: 'absolute', + width: TILE_W + 2, + height: TILE_H + 2, + left: left - 1, + top: top_ - 1, + clipPath: 'polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)', + backgroundColor: borderColor, + zIndex: zIdx - 1, + pointerEvents: 'none', + }; + + tiles.push( +
, +
!isCenter && onToggle(rx, ry) } + />, + ); + } + } + + return ( +
+ { tiles } +
+ ); +}; + +export const WiredSelectorUsersNeighborhoodView: FC<{}> = () => +{ + const [ selectedTiles, setSelectedTiles ] = useState([]); + const [ filterExisting, setFilterExisting ] = useState(false); + const [ invert, setInvert ] = useState(false); + const [ sourceType, setSourceType ] = useState(SOURCE_USER_TRIGGER); + const [ curX, setCurX ] = useState(0); + const [ curY, setCurY ] = useState(0); + + const { trigger = null, setIntParams } = useWired(); + + useEffect(() => + { + if(!trigger) return; + + const p = trigger.intData; + if(p.length >= 1) setSourceType(Math.min(p[0], SOURCE_USER_CLICKED)); + if(p.length >= 2) setFilterExisting(p[1] === 1); + if(p.length >= 3) setInvert(p[2] === 1); + + if(p.length >= 6) + { + const n = p[5]; + const tiles: Tile[] = []; + + for(let i = 0; i < n; i++) + { + const xi = 6 + i * 2; + if(xi + 1 < p.length) tiles.push({ x: p[xi], y: p[xi + 1] }); + } + + setSelectedTiles(tiles); + } + else + { + setSelectedTiles([]); + } + }, [ trigger ]); + + const save = useCallback(() => + { + const params: number[] = [ + sourceType, + filterExisting ? 1 : 0, + invert ? 1 : 0, + selectedTiles.length, + ...selectedTiles.flatMap(t => [ t.x, t.y ]), + ]; + + setIntParams(params); + }, [ sourceType, filterExisting, invert, selectedTiles, setIntParams ]); + + const toggleTile = useCallback((x: number, y: number) => + { + setSelectedTiles(prev => + tileIncluded(prev, x, y) + ? prev.filter(t => !(t.x === x && t.y === y)) + : [ ...prev, { x, y } ] + ); + }, []); + + const addTile = useCallback(() => + { + if(curX === 0 && curY === 0) return; + if(!tileIncluded(selectedTiles, curX, curY)) + setSelectedTiles(prev => [ ...prev, { x: curX, y: curY } ]); + }, [ curX, curY, selectedTiles ]); + + const removeTile = useCallback(() => + { + setSelectedTiles(prev => prev.filter(t => !(t.x === curX && t.y === curY))); + }, [ curX, curY ]); + + const clearTiles = useCallback(() => setSelectedTiles([]), []); + + const loadDefaultPattern = useCallback(() => + { + const tiles: Tile[] = []; + + for(let y = -2; y <= 2; y++) + { + for(let x = -2; x <= 2; x++) + { + if(x === 0 && y === 0) continue; + tiles.push({ x, y }); + } + } + + setSelectedTiles(tiles); + }, []); + + const sourceIndex = USER_SOURCES.findIndex(s => s.value === sourceType); + + const prevSource = () => + setSourceType(USER_SOURCES[(sourceIndex - 1 + USER_SOURCES.length) % USER_SOURCES.length].value); + + const nextSource = () => + setSourceType(USER_SOURCES[(sourceIndex + 1) % USER_SOURCES.length].value); + + return ( + +
+ + { LocalizeText('wiredfurni.params.neighborhood_selection') } + +
+ + + + +
+ +
+ +
+ +
+ X: + setCurX(parseInt(e.target.value) || 0) } /> + Y: + setCurY(parseInt(e.target.value) || 0) } /> +
+ +
+ + { LocalizeText('wiredfurni.params.selector_options_selector') } + + + + + +
+ + { LocalizeText('wiredfurni.params.sources.merged.title.neighborhood') } + +
+ +
+ { LocalizeText(USER_SOURCES[sourceIndex].label) } +
+ +
+ +
+
+ ); +};