feat: Enhance human event handling and animation management, including state updates and monitoring
This commit is contained in:
@@ -162,7 +162,6 @@ async function handleModelLoad(
|
||||
// SOCKET
|
||||
|
||||
if (selectedItem.type) {
|
||||
console.log('selectedItem: ', selectedItem);
|
||||
const data = PointsCalculator(
|
||||
selectedItem.type,
|
||||
gltf.scene.clone(),
|
||||
|
||||
@@ -49,6 +49,10 @@ function Model({ asset }: { readonly asset: Asset }) {
|
||||
const { userId, organization } = getUserData();
|
||||
const mixerRef = useRef<THREE.AnimationMixer>();
|
||||
const actions = useRef<{ [name: string]: THREE.AnimationAction }>({});
|
||||
const [currentAnimation, setCurrentAnimation] = useState<string | null>(null);
|
||||
const [previousAnimation, setPreviousAnimation] = useState<string | null>(null);
|
||||
const [blendFactor, setBlendFactor] = useState(0);
|
||||
const blendDuration = 0.3;
|
||||
|
||||
useEffect(() => {
|
||||
setDeletableFloorItem(null);
|
||||
@@ -278,8 +282,16 @@ function Model({ asset }: { readonly asset: Asset }) {
|
||||
}
|
||||
}
|
||||
|
||||
const handleAnimationComplete = useCallback(() => {
|
||||
console.log(`Animation "${currentAnimation}" completed`);
|
||||
}, [currentAnimation]);
|
||||
|
||||
useFrame((_, delta) => {
|
||||
if (mixerRef.current) {
|
||||
if (blendFactor < 1) {
|
||||
setBlendFactor(prev => Math.min(prev + delta / blendDuration, 1));
|
||||
}
|
||||
|
||||
mixerRef.current.update(delta);
|
||||
}
|
||||
});
|
||||
@@ -288,17 +300,46 @@ function Model({ asset }: { readonly asset: Asset }) {
|
||||
if (asset.animationState && asset.animationState.isPlaying) {
|
||||
if (!mixerRef.current) return;
|
||||
|
||||
Object.values(actions.current).forEach((action) => action.stop());
|
||||
|
||||
const action = actions.current[asset.animationState.current];
|
||||
if (action && asset.animationState?.isPlaying) {
|
||||
const loopMode = asset.animationState.loopAnimation ? THREE.LoopRepeat : THREE.LoopOnce;
|
||||
action.reset().setLoop(loopMode, loopMode === THREE.LoopRepeat ? Infinity : 1).play();
|
||||
if (asset.animationState.current !== currentAnimation) {
|
||||
setPreviousAnimation(currentAnimation);
|
||||
setCurrentAnimation(asset.animationState.current);
|
||||
setBlendFactor(0);
|
||||
}
|
||||
|
||||
const currentAction = actions.current[asset.animationState.current];
|
||||
const previousAction = previousAnimation ? actions.current[previousAnimation] : null;
|
||||
|
||||
if (currentAction) {
|
||||
const loopMode = asset.animationState.loopAnimation ? THREE.LoopRepeat : THREE.LoopOnce;
|
||||
|
||||
currentAction.reset();
|
||||
currentAction.setLoop(loopMode, loopMode === THREE.LoopRepeat ? Infinity : 1);
|
||||
|
||||
currentAction.play();
|
||||
|
||||
mixerRef.current.addEventListener('finished', handleAnimationComplete);
|
||||
|
||||
if (previousAction && blendFactor < 1) {
|
||||
previousAction.crossFadeTo(currentAction, blendDuration, true);
|
||||
}
|
||||
}
|
||||
|
||||
Object.entries(actions.current).forEach(([name, action]) => {
|
||||
if ((asset.animationState && name !== asset.animationState.current) && name !== previousAnimation) {
|
||||
action.stop();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Object.values(actions.current).forEach((action) => action.stop());
|
||||
setCurrentAnimation(null);
|
||||
}
|
||||
}, [asset.animationState])
|
||||
|
||||
return () => {
|
||||
if (mixerRef.current) {
|
||||
mixerRef.current.removeEventListener('finished', handleAnimationComplete);
|
||||
}
|
||||
}
|
||||
}, [asset.animationState, currentAnimation, previousAnimation, handleAnimationComplete]);
|
||||
|
||||
return (
|
||||
<group
|
||||
|
||||
@@ -4,8 +4,8 @@ import { useWorkerHandler } from './actionHandler/useWorkerHandler';
|
||||
export function useHumanActions() {
|
||||
const { handleWorker } = useWorkerHandler();
|
||||
|
||||
const handleWorkerAction = useCallback((action: HumanAction) => {
|
||||
handleWorker(action);
|
||||
const handleWorkerAction = useCallback((action: HumanAction, materialId: string) => {
|
||||
handleWorker(action, materialId);
|
||||
}, [handleWorker]);
|
||||
|
||||
const handleHumanAction = useCallback((action: HumanAction, materialId: string) => {
|
||||
@@ -13,7 +13,7 @@ export function useHumanActions() {
|
||||
|
||||
switch (action.actionType) {
|
||||
case 'worker':
|
||||
handleWorkerAction(action);
|
||||
handleWorkerAction(action, materialId);
|
||||
break;
|
||||
default:
|
||||
console.warn(`Unknown Human action type: ${action.actionType}`);
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useFrame } from '@react-three/fiber';
|
||||
import { usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../store/usePlayButtonStore';
|
||||
import { useSceneContext } from '../../../scene/sceneContext';
|
||||
|
||||
type HumanCallback = {
|
||||
humanId: string;
|
||||
actionId: string;
|
||||
callback: () => void;
|
||||
};
|
||||
|
||||
export function useHumanEventManager() {
|
||||
const { humanStore } = useSceneContext();
|
||||
const { getHumanById } = humanStore();
|
||||
const callbacksRef = useRef<HumanCallback[]>([]);
|
||||
const isMonitoringRef = useRef(false);
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
const { isPaused } = usePauseButtonStore();
|
||||
const { isReset } = useResetButtonStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (isReset) {
|
||||
callbacksRef.current = [];
|
||||
}
|
||||
}, [isReset])
|
||||
|
||||
// Add a new human to monitor
|
||||
const addHumanToMonitor = (humanId: string, actionId: string, callback: () => void) => {
|
||||
// Avoid duplicates
|
||||
if (!callbacksRef.current.some((entry) => entry.humanId === humanId)) {
|
||||
callbacksRef.current.push({ humanId, actionId, callback });
|
||||
}
|
||||
|
||||
// Start monitoring if not already running
|
||||
if (!isMonitoringRef.current) {
|
||||
isMonitoringRef.current = true;
|
||||
}
|
||||
};
|
||||
|
||||
// Remove a human from monitoring
|
||||
const removeHumanFromMonitor = (humanId: string) => {
|
||||
callbacksRef.current = callbacksRef.current.filter(
|
||||
(entry) => entry.humanId !== humanId
|
||||
);
|
||||
|
||||
// Stop monitoring if no more humans to track
|
||||
if (callbacksRef.current.length === 0) {
|
||||
isMonitoringRef.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Check human states every frame
|
||||
useFrame(() => {
|
||||
if (!isMonitoringRef.current || callbacksRef.current.length === 0 || !isPlaying || isPaused) return;
|
||||
|
||||
callbacksRef.current.forEach(({ humanId, actionId, callback }) => {
|
||||
const human = getHumanById(humanId);
|
||||
if (!human) return;
|
||||
const action = human.point.actions.find((action) => action.actionUuid === actionId);
|
||||
if (action && human.isActive === false && human.state === 'idle' && human.isPicking && human.currentLoad < action.loadCapacity) {
|
||||
callback();
|
||||
removeHumanFromMonitor(humanId); // Remove after triggering
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Cleanup on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
callbacksRef.current = [];
|
||||
isMonitoringRef.current = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return {
|
||||
addHumanToMonitor,
|
||||
removeHumanFromMonitor,
|
||||
};
|
||||
}
|
||||
@@ -1,16 +1,113 @@
|
||||
import { useEffect } from 'react'
|
||||
import HumanUi from './humanUi';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import * as THREE from 'three';
|
||||
import { NavMeshQuery } from '@recast-navigation/core';
|
||||
import { useNavMesh } from '../../../../../store/builder/store';
|
||||
import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from '../../../../../store/usePlayButtonStore';
|
||||
import { useTriggerHandler } from '../../../triggers/triggerHandler/useTriggerHandler';
|
||||
import { useSceneContext } from '../../../../scene/sceneContext';
|
||||
import { useProductContext } from '../../../products/productContext';
|
||||
|
||||
import HumanAnimator from './animator/humanAnimator';
|
||||
|
||||
function HumanInstance({ human }: { human: HumanStatus }) {
|
||||
const { navMesh } = useNavMesh();
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
const { materialStore, armBotStore, conveyorStore, vehicleStore, humanStore, storageUnitStore, productStore } = useSceneContext();
|
||||
const { removeMaterial, setEndTime } = materialStore();
|
||||
const { getStorageUnitById } = storageUnitStore();
|
||||
const { getArmBotById } = armBotStore();
|
||||
const { getConveyorById } = conveyorStore();
|
||||
const { getVehicleById } = vehicleStore();
|
||||
const { triggerPointActions } = useTriggerHandler();
|
||||
const { getActionByUuid, getEventByModelUuid, getTriggerByUuid } = productStore();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
const { humans, setHumanActive, setHumanState, setHumanPicking, clearCurrentMaterials, setHumanLoad, decrementHumanLoad, removeLastMaterial, getLastMaterial, incrementIdleTime, incrementActiveTime, resetTime } = humanStore();
|
||||
|
||||
const [currentPhase, setCurrentPhase] = useState<string>('init');
|
||||
const [path, setPath] = useState<[number, number, number][]>([]);
|
||||
const pauseTimeRef = useRef<number | null>(null);
|
||||
const idleTimeRef = useRef<number>(0);
|
||||
const activeTimeRef = useRef<number>(0);
|
||||
const isPausedRef = useRef<boolean>(false);
|
||||
const isSpeedRef = useRef<number>(0);
|
||||
let startTime: number;
|
||||
let fixedInterval: number;
|
||||
const { speed } = useAnimationPlaySpeed();
|
||||
const { isPaused } = usePauseButtonStore();
|
||||
const previousTimeRef = useRef<number | null>(null);
|
||||
const animationFrameIdRef = useRef<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('human: ', human);
|
||||
}, [human])
|
||||
isPausedRef.current = isPaused;
|
||||
}, [isPaused]);
|
||||
|
||||
useEffect(() => {
|
||||
isSpeedRef.current = speed;
|
||||
}, [speed]);
|
||||
|
||||
const computePath = useCallback(
|
||||
(start: any, end: any) => {
|
||||
try {
|
||||
const navMeshQuery = new NavMeshQuery(navMesh);
|
||||
const { path: segmentPath } = navMeshQuery.computePath(start, end);
|
||||
if (
|
||||
segmentPath.length > 0 &&
|
||||
Math.round(segmentPath[segmentPath.length - 1].x) == Math.round(end.x) &&
|
||||
Math.round(segmentPath[segmentPath.length - 1].z) == Math.round(end.z)
|
||||
) {
|
||||
return segmentPath?.map(({ x, y, z }) => [x, 0, z] as [number, number, number]) || [];
|
||||
} else {
|
||||
console.log("There is no path here...Choose valid path")
|
||||
const { path: segmentPaths } = navMeshQuery.computePath(start, start);
|
||||
return segmentPaths.map(({ x, y, z }) => [x, 0, z] as [number, number, number]) || [];
|
||||
}
|
||||
} catch {
|
||||
console.error("Failed to compute path");
|
||||
return [];
|
||||
}
|
||||
},
|
||||
[navMesh]
|
||||
);
|
||||
|
||||
function humanStatus(modelId: string, status: string) {
|
||||
// console.log(`${modelId} , ${status}`);
|
||||
}
|
||||
|
||||
function reset() {
|
||||
setCurrentPhase('init');
|
||||
setHumanActive(human.modelUuid, false);
|
||||
setHumanPicking(human.modelUuid, false);
|
||||
setHumanState(human.modelUuid, 'idle');
|
||||
setHumanLoad(human.modelUuid, 0);
|
||||
setPath([]);
|
||||
startTime = 0;
|
||||
isPausedRef.current = false;
|
||||
pauseTimeRef.current = 0;
|
||||
resetTime(human.modelUuid)
|
||||
activeTimeRef.current = 0
|
||||
idleTimeRef.current = 0
|
||||
previousTimeRef.current = null
|
||||
if (animationFrameIdRef.current !== null) {
|
||||
cancelAnimationFrame(animationFrameIdRef.current)
|
||||
animationFrameIdRef.current = null
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (isPlaying) {
|
||||
|
||||
}
|
||||
else {
|
||||
reset()
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [human, currentPhase, path, isPlaying]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
<HumanUi />
|
||||
<HumanAnimator />
|
||||
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -124,7 +124,8 @@ function determineExecutionMachineSequences(products: productsSchema): EventsSch
|
||||
event.type === 'vehicle' ||
|
||||
event.type === 'machine' ||
|
||||
event.type === 'storageUnit' ||
|
||||
event.type === 'roboticArm'
|
||||
event.type === 'roboticArm' ||
|
||||
event.type === 'human'
|
||||
) {
|
||||
pointToEventMap.set(event.point.uuid, event);
|
||||
allPoints.push(event.point);
|
||||
|
||||
@@ -16,7 +16,8 @@ export async function determineExecutionMachineSequences(products: productsSchem
|
||||
event.type === 'vehicle' ||
|
||||
event.type === 'machine' ||
|
||||
event.type === 'storageUnit' ||
|
||||
event.type === 'roboticArm'
|
||||
event.type === 'roboticArm' ||
|
||||
event.type === 'human'
|
||||
) {
|
||||
pointToEventMap.set(event.point.uuid, event);
|
||||
allPoints.push(event.point);
|
||||
|
||||
@@ -19,7 +19,9 @@ export function determineExecutionOrder(products: productsSchema): PointsScheme[
|
||||
} else if (event.type === 'vehicle' ||
|
||||
event.type === 'machine' ||
|
||||
event.type === 'storageUnit' ||
|
||||
event.type === 'roboticArm') {
|
||||
event.type === 'roboticArm' ||
|
||||
event.type === 'human'
|
||||
) {
|
||||
pointMap.set(event.point.uuid, event.point);
|
||||
allPoints.push(event.point);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,9 @@ export async function determineExecutionSequences(products: productsSchema): Pro
|
||||
} else if (event.type === 'vehicle' ||
|
||||
event.type === 'machine' ||
|
||||
event.type === 'storageUnit' ||
|
||||
event.type === 'roboticArm') {
|
||||
event.type === 'roboticArm' ||
|
||||
event.type === 'human'
|
||||
) {
|
||||
pointMap.set(event.point.uuid, event.point);
|
||||
allPoints.push(event.point);
|
||||
}
|
||||
|
||||
@@ -91,7 +91,8 @@ function determineExecutionMachineSequences(products: productsSchema): EventsSch
|
||||
event.type === 'vehicle' ||
|
||||
event.type === 'machine' ||
|
||||
event.type === 'storageUnit' ||
|
||||
event.type === 'roboticArm'
|
||||
event.type === 'roboticArm' ||
|
||||
event.type === 'human'
|
||||
) {
|
||||
pointToEventMap.set(event.point.uuid, event);
|
||||
allPoints.push(event.point);
|
||||
|
||||
@@ -6,9 +6,10 @@ import { useVehicleEventManager } from '../../vehicle/eventManager/useVehicleEve
|
||||
import { useMachineEventManager } from '../../machine/eventManager/useMachineEventManager';
|
||||
import { useSceneContext } from '../../../scene/sceneContext';
|
||||
import { useProductContext } from '../../products/productContext';
|
||||
import { useHumanEventManager } from '../../human/eventManager/useHumanEventManager';
|
||||
|
||||
export function useTriggerHandler() {
|
||||
const { materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, productStore } = useSceneContext();
|
||||
const { materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, humanStore, storageUnitStore, productStore } = useSceneContext();
|
||||
const { selectedProductStore } = useProductContext();
|
||||
const { handleAction } = useActionHandler();
|
||||
const { selectedProduct } = selectedProductStore();
|
||||
@@ -19,7 +20,9 @@ export function useTriggerHandler() {
|
||||
const { addConveyorToMonitor } = useConveyorEventManager();
|
||||
const { addVehicleToMonitor } = useVehicleEventManager();
|
||||
const { addMachineToMonitor } = useMachineEventManager();
|
||||
const { addHumanToMonitor } = useHumanEventManager();
|
||||
const { getVehicleById } = vehicleStore();
|
||||
const { getHumanById } = humanStore();
|
||||
const { getMachineById } = machineStore();
|
||||
const { getStorageUnitById } = storageUnitStore();
|
||||
const { getMaterialById, setCurrentLocation, setNextLocation, setPreviousLocation, setIsPaused, setIsVisible, setEndTime } = materialStore();
|
||||
@@ -256,6 +259,57 @@ export function useTriggerHandler() {
|
||||
} else if (toEvent?.type === 'storageUnit') {
|
||||
// Transfer to Storage Unit
|
||||
|
||||
} else if (toEvent?.type === 'human') {
|
||||
// Transfer to Human
|
||||
if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) {
|
||||
const material = getMaterialById(materialId);
|
||||
if (material) {
|
||||
const triggeredAction = action;
|
||||
|
||||
// Handle current action of the material
|
||||
handleAction(action, materialId);
|
||||
|
||||
if (material.next) {
|
||||
const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid);
|
||||
const human = getHumanById(trigger.triggeredAsset?.triggeredModel.modelUuid);
|
||||
|
||||
setPreviousLocation(material.materialId, {
|
||||
modelUuid: material.current.modelUuid,
|
||||
pointUuid: material.current.pointUuid,
|
||||
actionUuid: material.current.actionUuid,
|
||||
})
|
||||
|
||||
setCurrentLocation(material.materialId, {
|
||||
modelUuid: material.next.modelUuid,
|
||||
pointUuid: material.next.pointUuid,
|
||||
actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid,
|
||||
});
|
||||
|
||||
setNextLocation(material.materialId, null);
|
||||
|
||||
if (action) {
|
||||
|
||||
if (human) {
|
||||
|
||||
if (human.isActive === false && human.state === 'idle' && human.isPicking && human.currentLoad < (triggeredAction as HumanAction).loadCapacity) {
|
||||
|
||||
setIsVisible(materialId, false);
|
||||
|
||||
// Handle current action from vehicle
|
||||
handleAction(action, materialId);
|
||||
|
||||
} else {
|
||||
|
||||
// Handle current action using Event Manager
|
||||
addHumanToMonitor(human.modelUuid, triggeredAction.actionUuid, () => {
|
||||
handleAction(action, materialId);
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (fromEvent?.type === 'vehicle') {
|
||||
if (toEvent?.type === 'transfer') {
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import VehicleAnimator from '../animator/vehicleAnimator';
|
||||
import * as THREE from 'three';
|
||||
import { NavMeshQuery } from '@recast-navigation/core';
|
||||
import { useNavMesh } from '../../../../../store/builder/store';
|
||||
import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore } from '../../../../../store/usePlayButtonStore';
|
||||
import { useTriggerHandler } from '../../../triggers/triggerHandler/useTriggerHandler';
|
||||
import MaterialAnimator from '../animator/materialAnimator';
|
||||
import { useSceneContext } from '../../../../scene/sceneContext';
|
||||
import { useProductContext } from '../../../products/productContext';
|
||||
|
||||
import MaterialAnimator from '../animator/materialAnimator';
|
||||
import VehicleAnimator from '../animator/vehicleAnimator';
|
||||
|
||||
function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>) {
|
||||
const { navMesh } = useNavMesh();
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
@@ -103,10 +104,6 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>)
|
||||
new THREE.Vector3(agvDetail?.position[0], agvDetail?.position[1], agvDetail?.position[2]),
|
||||
agvDetail?.point?.action?.pickUpPoint?.position
|
||||
);
|
||||
// const toPickupPath = computePath(
|
||||
// new THREE.Vector3(agvDetail?.position[0], agvDetail?.position[1], agvDetail?.position[2]),
|
||||
// new THREE.Vector3(agvDetail?.position[0], agvDetail?.position[1], agvDetail?.position[2])
|
||||
// );
|
||||
setPath(toPickupPath);
|
||||
setCurrentPhase('stationed-pickup');
|
||||
setVehicleState(agvDetail.modelUuid, 'running');
|
||||
@@ -150,7 +147,6 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [vehicles, currentPhase, path, isPlaying]);
|
||||
|
||||
|
||||
function animate(currentTime: number) {
|
||||
if (previousTimeRef.current === null) {
|
||||
previousTimeRef.current = currentTime;
|
||||
@@ -527,10 +523,4 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>)
|
||||
);
|
||||
}
|
||||
|
||||
export default VehicleInstance;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export default VehicleInstance;
|
||||
@@ -15,6 +15,10 @@ interface HumansStore {
|
||||
setHumanActive: (modelUuid: string, isActive: boolean) => void;
|
||||
setHumanPicking: (modelUuid: string, isPicking: boolean) => void;
|
||||
setHumanLoad: (modelUuid: string, load: number) => void;
|
||||
setHumanState: (
|
||||
modelUuid: string,
|
||||
newState: HumanStatus["state"]
|
||||
) => void;
|
||||
incrementHumanLoad: (modelUuid: string, incrementBy: number) => void;
|
||||
decrementHumanLoad: (modelUuid: string, decrementBy: number) => void;
|
||||
|
||||
@@ -106,6 +110,15 @@ export const createHumanStore = () => {
|
||||
});
|
||||
},
|
||||
|
||||
setHumanState: (modelUuid, newState) => {
|
||||
set((state) => {
|
||||
const human = state.humans.find(h => h.modelUuid === modelUuid);
|
||||
if (human) {
|
||||
human.state = newState;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
incrementHumanLoad: (modelUuid, incrementBy) => {
|
||||
set((state) => {
|
||||
const human = state.humans.find(h => h.modelUuid === modelUuid);
|
||||
|
||||
Reference in New Issue
Block a user