diff --git a/src/icons/currentPosition.svg b/src/assets/icons/currentPosition.svg similarity index 100% rename from src/icons/currentPosition.svg rename to src/assets/icons/currentPosition.svg diff --git a/src/assets/icons/danger.svg b/src/assets/icons/danger.svg new file mode 100644 index 0000000..435971e --- /dev/null +++ b/src/assets/icons/danger.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/icons/drone.svg b/src/assets/icons/drone.svg new file mode 100644 index 0000000..76d3ec3 --- /dev/null +++ b/src/assets/icons/drone.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/enemy.svg b/src/assets/icons/enemy.svg new file mode 100644 index 0000000..56e4ab8 --- /dev/null +++ b/src/assets/icons/enemy.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/enemySelected.svg b/src/assets/icons/enemySelected.svg new file mode 100644 index 0000000..13e90e3 --- /dev/null +++ b/src/assets/icons/enemySelected.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/assets/minimap-bg.jpg b/src/assets/minimap-bg.jpg new file mode 100644 index 0000000..d4d365d Binary files /dev/null and b/src/assets/minimap-bg.jpg differ diff --git a/src/components/Widget/VehicleWidget.tsx b/src/components/Widget/VehicleWidget.tsx index 3528c17..6a4c637 100644 --- a/src/components/Widget/VehicleWidget.tsx +++ b/src/components/Widget/VehicleWidget.tsx @@ -1,9 +1,9 @@ import Element from 'src/components/Element/Element'; -import type { Widget } from 'src/types/widget'; +import type { VehicleWidget as VehicleWidgetType } from 'src/types/widget'; // widget.type must be 'vehicle' type VehicleWidgetProps = { - widget: Widget; + widget: VehicleWidgetType; }; const VehicleWidget = ({ widget }: VehicleWidgetProps) => { @@ -15,10 +15,11 @@ const VehicleWidget = ({ widget }: VehicleWidgetProps) => { id={widget.id} className={className} style={{ - height: widget.h, - width: widget.w, + height: ~~widget.h, + width: ~~widget.w, top: widget.y, left: widget.x, + transform: `rotate(${-widget.rotation + Math.PI * 0.5}rad)`, }} > diff --git a/src/hooks/useMoveShips.ts b/src/hooks/useMoveShips.ts index 70375bd..cc33b7e 100644 --- a/src/hooks/useMoveShips.ts +++ b/src/hooks/useMoveShips.ts @@ -5,92 +5,58 @@ import { getOwnship, updateShipPosition, } from 'src/redux/slices/minimapSlice'; -import { OWNSHIP_TRAJECTORY } from 'src/utils/constants'; +import type { VehicleWidget } from 'src/types/widget'; +import { SHIP_BOUNDS } from 'src/utils/constants'; const useMoveShips = () => { const dispatch = useAppDispatch(); const ownship = useAppSelector(getOwnship); const drones = useAppSelector(getDrones); + const updatePosition = (ship: VehicleWidget) => { + let dx = Math.cos(ship.rotation) * ship.speed; + let dy = Math.sin(ship.rotation) * ship.speed; + if (ship.x <= SHIP_BOUNDS.left) dx = Math.abs(dx); + else if (ship.x >= SHIP_BOUNDS.right) dx = -Math.abs(dx); + if (ship.y <= SHIP_BOUNDS.top) dy = -Math.abs(dy); + else if (ship.y >= SHIP_BOUNDS.bottom) dy = Math.abs(dy); + const newRotation = Math.atan2(dy, dx); + + dispatch( + updateShipPosition(ship.id, ship.x + dx, ship.y - dy, newRotation), + ); + }; + useEffect(() => { console.log('drones', drones); }, [drones.length]); useEffect(() => { - if (!ownship) return; + if (!ownship || ownship.type !== 'vehicle') return; - // update ownship position every 500ms (0.5s) const timer = setInterval(() => { - if ( - ownship.x + OWNSHIP_TRAJECTORY.xSpeed <= OWNSHIP_TRAJECTORY.end[0] && - ownship.y - OWNSHIP_TRAJECTORY.ySpeed >= OWNSHIP_TRAJECTORY.end[1] - ) { - // only update ownship position if within bounds - dispatch( - updateShipPosition( - ownship.id, - ownship.x + OWNSHIP_TRAJECTORY.xSpeed, - ownship.y - OWNSHIP_TRAJECTORY.ySpeed, - ), - ); - } - }, 500); + updatePosition(ownship); + }, 100); return () => clearInterval(timer); }, [ownship, dispatch]); useEffect(() => { - if (!drones) return; + if (!drones || drones.some((widget) => widget.type !== 'vehicle')) return; - // random drone movement every 1500ms (1.5s) + // random drone movement every 100ms const timer = setInterval(() => { - const bounds = { - left: 400, - right: 1920, - top: 1080, - bottom: 50, - }; - const droneMove = { - x: 5, - y: 0, - }; - drones.forEach((drone) => { - droneMove.x = Math.floor(Math.random() * 10) - 5; - droneMove.y = Math.floor(Math.random() * 10) - 5; - - if (!drone) return; - - // only move drone if within defined bounds - if ( - drone.x + droneMove.x < bounds.left || - drone.x + droneMove.x > bounds.right - ) { - droneMove.x = -droneMove.x; - } - if ( - drone.y + droneMove.y < bounds.bottom || - drone.y + droneMove.y > bounds.top - ) { - droneMove.y = -droneMove.y; - } - - dispatch( - updateShipPosition( - drone.id, - drone.x + droneMove.x, - drone.y + droneMove.y, - ), - ); + updatePosition(drone as VehicleWidget); }); - }, 1500); + }, 100); return () => clearInterval(timer); // dependencies omitted because drones array is changing too frequently // some warning/issue of selector returning different values despite same parameters /* eslint-disable-next-line react-hooks/exhaustive-deps */ - }, [drones.length]); + }, [drones[0], drones.length]); }; export default useMoveShips; diff --git a/src/icons/drone.svg b/src/icons/drone.svg index 76d3ec3..7da25ce 100644 --- a/src/icons/drone.svg +++ b/src/icons/drone.svg @@ -1,3 +1,29 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/icons/ownship.svg b/src/icons/ownship.svg new file mode 100644 index 0000000..2be1d1f --- /dev/null +++ b/src/icons/ownship.svg @@ -0,0 +1,209 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/pages/Minimap.tsx b/src/pages/Minimap.tsx index b1f66b5..0b11ce6 100644 --- a/src/pages/Minimap.tsx +++ b/src/pages/Minimap.tsx @@ -5,6 +5,7 @@ import useMoveShips from 'src/hooks/useMoveShips'; import useGaze from 'src/hooks/useGaze'; import { getElementsInGaze } from 'src/redux/slices/gazeSlice'; import { useEffect } from 'react'; +import background from 'src/assets/minimap-bg.jpg'; const Minimap = () => { const widgets = useAppSelector((state) => @@ -22,7 +23,10 @@ const Minimap = () => { }, [elementsInGaze]); return ( -
+
{Object.keys(widgets).map((widgetId) => ( ))} diff --git a/src/redux/slices/minimapSlice.ts b/src/redux/slices/minimapSlice.ts index e374987..4800704 100644 --- a/src/redux/slices/minimapSlice.ts +++ b/src/redux/slices/minimapSlice.ts @@ -66,17 +66,22 @@ export const minimapSlice = createSlice({ }, updateShipPosition: { - prepare(shipId: string, x: number, y: number) { + prepare(shipId: string, x: number, y: number, rotation: number) { return { - payload: { shipId, x, y }, + payload: { shipId, x, y, rotation }, }; }, reducer: ( state, - action: PayloadAction<{ shipId: string; x: number; y: number }>, + action: PayloadAction<{ + shipId: string; + x: number; + y: number; + rotation: number; + }>, ) => { - const { shipId, x, y } = action.payload; + const { shipId, x, y, rotation } = action.payload; const ship = state.widgets[shipId]; // check if ship exists @@ -95,6 +100,7 @@ export const minimapSlice = createSlice({ ...ship, x, y, + rotation, }; }, }, diff --git a/src/types/widget.ts b/src/types/widget.ts index a9cefde..285f519 100644 --- a/src/types/widget.ts +++ b/src/types/widget.ts @@ -38,7 +38,8 @@ export type VehicleWidget = BaseWidget & { type: 'vehicle'; // this corresponds to the id in the schema-types defined by the world-sim team vehicleId: number; - // additonal properties... + speed: number; + rotation: number; // rad }; export type CustomWidget = BaseWidget & { diff --git a/src/ui/Gaze.tsx b/src/ui/Gaze.tsx index d2bb9bb..2ec8e20 100644 --- a/src/ui/Gaze.tsx +++ b/src/ui/Gaze.tsx @@ -15,7 +15,7 @@ const Gaze = ({ mousePosition }: GazeProps) => { return (
({ type: 'icon', @@ -33,24 +28,29 @@ const createDroneWidget = ( w: number, h: number, vehicleId: number, - id: string, -): VehicleWidget => ({ - elements: [createDroneElement(id)], - id, - sectionType: 'free', - type: 'vehicle', - screen: '/minimap', - vehicleId, +): VehicleWidget => { + const id = uuid(); + return { + elements: [createDroneElement(id)], + id, + sectionType: 'free', + type: 'vehicle', + screen: '/minimap', + vehicleId, - x, - y, - w, - h, + x, + y, + w, + h, - canOverlap: false, - useElementLocation: false, - maxAmount: 10, -}); + speed: Math.random() * 0.5 + 0.25, + rotation: Math.random() * Math.PI, + + canOverlap: false, + useElementLocation: false, + maxAmount: 10, + }; +}; const ownshipElement: IconElement = { type: 'icon', @@ -59,23 +59,26 @@ const ownshipElement: IconElement = { src: OWNSHIP_LOGO, tag: 'ownship', - widgetId: uuid1, + widgetId: ownshipUuid, - h: 50, - w: 50, + h: 56, + w: 56, xWidget: 0, yWidget: 0, }; export const ownship: VehicleWidget = { - id: uuid1, + id: ownshipUuid, vehicleId: 0, x: 400, y: 950, - w: 50, - h: 50, + w: 56, + h: 56, + + speed: 1.5, + rotation: 0.2 * Math.PI, // 36deg screen: '/minimap', sectionType: 'free', @@ -87,13 +90,14 @@ export const ownship: VehicleWidget = { maxAmount: 10, }; -const drone1 = createDroneWidget(500, 200, 50, 50, 1, uuid2); -const drone2 = createDroneWidget(1500, 550, 50, 50, 2, uuid3); -const drone3 = createDroneWidget(1500, 350, 50, 50, 3, uuid4); -const drone4 = createDroneWidget(200, 900, 50, 50, 4, uuid5); -const drone5 = createDroneWidget(1150, 750, 50, 50, 5, uuid6); +const drone1 = createDroneWidget(500, 300, 80, 80, 1); +const drone2 = createDroneWidget(1500, 550, 80, 80, 2); +const drone3 = createDroneWidget(1500, 350, 80, 80, 3); +const drone4 = createDroneWidget(200, 900, 80, 80, 4); +const drone5 = createDroneWidget(1150, 750, 80, 80, 5); +const drone6 = createDroneWidget(750, 400, 80, 80, 6); -export const drones = [drone1, drone2, drone3, drone4, drone5]; +export const drones = [drone1, drone2, drone3, drone4, drone5, drone6]; export const initialShips: WidgetMap = { [ownship.id]: ownship, @@ -102,4 +106,5 @@ export const initialShips: WidgetMap = { [drone3.id]: drone3, [drone4.id]: drone4, [drone5.id]: drone5, + [drone6.id]: drone6, }; diff --git a/vite.config.ts b/vite.config.ts index 6011100..7c649b1 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -14,9 +14,9 @@ export default defineConfig({ overlay: false, }), ], - server: { - open: true, - }, + //server: { + // open: true, + //}, test: { globals: true, environment: 'jsdom',