Merge branch 'main' into pr/wired-freeze-furni-movement-ui-clean-20260318

This commit is contained in:
DuckieTM
2026-03-19 10:03:32 +01:00
committed by GitHub
65 changed files with 4866 additions and 526 deletions
@@ -7,6 +7,131 @@ import { NitroInput } from '../../../../layout';
import { WiredActionBaseView } from './WiredActionBaseView';
import { WiredSourcesSelector } from '../WiredSourcesSelector';
type RewardType = 'badge' | 'credits' | 'pixels' | 'diamonds' | 'points' | 'furni' | 'respect';
interface RewardEntry
{
rewardType: RewardType;
rewardValue: string;
probability: number;
pointsType: number;
}
const DEFAULT_PROBABILITY = 100;
const DEFAULT_POINTS_TYPE = 5;
const REWARD_TYPES: { value: RewardType, label: string }[] = [
{ value: 'badge', label: 'Badge' },
{ value: 'credits', label: 'Credits' },
{ value: 'pixels', label: 'Pixels / Duckets' },
{ value: 'diamonds', label: 'Diamonds' },
{ value: 'points', label: 'Extra Currency' },
{ value: 'furni', label: 'Furni' },
{ value: 'respect', label: 'Respect' }
];
const SELECTABLE_REWARD_TYPES = REWARD_TYPES.filter(entry => (entry.value !== 'respect'));
const createReward = (): RewardEntry =>
({
rewardType: 'furni',
rewardValue: '',
probability: DEFAULT_PROBABILITY,
pointsType: DEFAULT_POINTS_TYPE
});
const getRewardValuePlaceholder = (rewardType: RewardType) =>
{
switch(rewardType)
{
case 'badge':
return 'Badge code';
case 'credits':
return 'Credits amount';
case 'pixels':
return 'Pixels amount';
case 'diamonds':
return 'Diamonds amount';
case 'points':
return 'Amount';
case 'furni':
return 'Furni base item id';
case 'respect':
return 'Respect amount';
}
};
const getExtraFieldLabel = (rewardType: RewardType) =>
{
switch(rewardType)
{
case 'points':
return 'Currency Type';
case 'badge':
return 'Code';
default:
return 'Info';
}
};
const getExtraFieldPlaceholder = (rewardType: RewardType) =>
{
switch(rewardType)
{
case 'points':
return 'Type id (e.g. 105)';
case 'badge':
return 'Badge';
default:
return '';
}
};
const parseRewardEntry = (rawType: string, rawCode: string, rawProbability: string): RewardEntry =>
{
const probability = Number(rawProbability);
const parsedProbability = Number.isFinite(probability) ? probability : DEFAULT_PROBABILITY;
if(rawType === '0')
{
return { rewardType: 'badge', rewardValue: rawCode, probability: parsedProbability, pointsType: DEFAULT_POINTS_TYPE };
}
const separatorIndex = rawCode.indexOf('#');
if(separatorIndex === -1)
{
return { rewardType: 'furni', rewardValue: rawCode, probability: parsedProbability, pointsType: DEFAULT_POINTS_TYPE };
}
const rewardType = rawCode.slice(0, separatorIndex);
const rewardValue = rawCode.slice(separatorIndex + 1);
if(rewardType.startsWith('points'))
{
const pointsType = Number(rewardType.slice('points'.length));
return {
rewardType: 'points',
rewardValue,
probability: parsedProbability,
pointsType: Number.isFinite(pointsType) ? pointsType : DEFAULT_POINTS_TYPE
};
}
if(REWARD_TYPES.some(entry => (entry.value === rewardType)))
{
return { rewardType: rewardType as RewardType, rewardValue, probability: parsedProbability, pointsType: DEFAULT_POINTS_TYPE };
}
if(rewardType === 'cata')
{
return { rewardType: 'furni', rewardValue, probability: parsedProbability, pointsType: DEFAULT_POINTS_TYPE };
}
return { rewardType: 'furni', rewardValue: rawCode, probability: parsedProbability, pointsType: DEFAULT_POINTS_TYPE };
};
export const WiredActionGiveRewardView: FC<{}> = props =>
{
const [ limitEnabled, setLimitEnabled ] = useState(false);
@@ -14,7 +139,7 @@ export const WiredActionGiveRewardView: FC<{}> = props =>
const [ uniqueRewards, setUniqueRewards ] = useState(false);
const [ rewardsLimit, setRewardsLimit ] = useState(1);
const [ limitationInterval, setLimitationInterval ] = useState(1);
const [ rewards, setRewards ] = useState<{ isBadge: boolean, itemCode: string, probability: number }[]>([]);
const [ rewards, setRewards ] = useState<RewardEntry[]>([]);
const { trigger = null, setIntParams = null, setStringParam = null } = useWired();
const [ userSource, setUserSource ] = useState<number>(() =>
{
@@ -22,7 +147,8 @@ export const WiredActionGiveRewardView: FC<{}> = props =>
return 0;
});
const addReward = () => setRewards(rewards => [ ...rewards, { isBadge: false, itemCode: '', probability: null } ]);
const addReward = () => setRewards(rewards => [ ...rewards, createReward() ]);
const hasCustomCurrencyReward = rewards.some(reward => (reward.rewardType === 'points'));
const removeReward = (index: number) =>
{
@@ -36,18 +162,9 @@ export const WiredActionGiveRewardView: FC<{}> = props =>
});
};
const updateReward = (index: number, isBadge: boolean, itemCode: string, probability: number) =>
const updateReward = (index: number, updater: (reward: RewardEntry) => RewardEntry) =>
{
const rewardsClone = Array.from(rewards);
const reward = rewardsClone[index];
if(!reward) return;
reward.isBadge = isBadge;
reward.itemCode = itemCode;
reward.probability = probability;
setRewards(rewardsClone);
setRewards(prevValue => prevValue.map((reward, rewardIndex) => ((rewardIndex === index) ? updater(reward) : reward)));
};
const save = () =>
@@ -56,9 +173,20 @@ export const WiredActionGiveRewardView: FC<{}> = props =>
for(const reward of rewards)
{
if(!reward.itemCode) continue;
const rewardValue = reward.rewardValue.trim();
const rewardsString = [ reward.isBadge ? '0' : '1', reward.itemCode, reward.probability.toString() ];
if(!rewardValue) continue;
const probability = Math.max(0, Number.isFinite(reward.probability) ? reward.probability : DEFAULT_PROBABILITY);
const rewardCode = (() =>
{
if(reward.rewardType === 'badge') return rewardValue;
if(reward.rewardType === 'points') return `points${ Math.max(0, reward.pointsType) }#${ rewardValue }`;
return `${ reward.rewardType }#${ rewardValue }`;
})();
const rewardsString = [ reward.rewardType === 'badge' ? '0' : '1', rewardCode, (uniqueRewards ? DEFAULT_PROBABILITY : probability).toString() ];
stringRewards.push(rewardsString.join(','));
}
@@ -71,9 +199,9 @@ export const WiredActionGiveRewardView: FC<{}> = props =>
useEffect(() =>
{
const readRewards: { isBadge: boolean, itemCode: string, probability: number }[] = [];
const readRewards: RewardEntry[] = [];
if(trigger.stringData.length > 0 && trigger.stringData.includes(';'))
if(trigger.stringData.length > 0)
{
const splittedRewards = trigger.stringData.split(';');
@@ -83,11 +211,11 @@ export const WiredActionGiveRewardView: FC<{}> = props =>
if(reward.length !== 3) continue;
readRewards.push({ isBadge: reward[0] === '0', itemCode: reward[1], probability: Number(reward[2]) });
readRewards.push(parseRewardEntry(reward[0], reward[1], reward[2]));
}
}
if(readRewards.length === 0) readRewards.push({ isBadge: false, itemCode: '', probability: null });
if(readRewards.length === 0) readRewards.push(createReward());
setRewardTime((trigger.intData.length > 0) ? trigger.intData[0] : 0);
setUniqueRewards((trigger.intData.length > 1) ? (trigger.intData[1] === 1) : false);
@@ -147,24 +275,64 @@ export const WiredActionGiveRewardView: FC<{}> = props =>
</Button>
</div>
<div className="flex flex-col gap-1">
<div className="grid grid-cols-[1.2fr_1fr_110px_150px_42px] gap-1 px-1">
<Text small bold>Type</Text>
<Text small bold>Amount / Value</Text>
<Text small bold>{ uniqueRewards ? 'Mode' : 'Chance %' }</Text>
<Text small bold>{ hasCustomCurrencyReward ? 'Currency Type' : 'Extra / Info' }</Text>
<Text small bold>Action</Text>
</div>
{ rewards && rewards.map((reward, index) =>
{
const rewardTypeOptions = (reward.rewardType === 'respect')
? REWARD_TYPES
: SELECTABLE_REWARD_TYPES;
return (
<div key={ index } className="flex gap-1">
<div className="flex items-center gap-1">
<input checked={ reward.isBadge } className="form-check-input" type="checkbox" onChange={ (e) => updateReward(index, e.target.checked, reward.itemCode, reward.probability) } />
<Text small>Badge?</Text>
<div key={ index } className="grid grid-cols-[1.2fr_1fr_110px_150px_42px] gap-1">
<select className="w-full form-select form-select-sm" value={ reward.rewardType } onChange={ event => updateReward(index, prevValue => ({ ...prevValue, rewardType: event.target.value as RewardType, rewardValue: '' })) }>
{ rewardTypeOptions.map(entry => <option key={ entry.value } value={ entry.value }>{ entry.label }</option>) }
</select>
<NitroInput
placeholder={ getRewardValuePlaceholder(reward.rewardType) }
type={ reward.rewardType === 'badge' ? 'text' : 'number' }
value={ reward.rewardValue }
onChange={ event => updateReward(index, prevValue => ({ ...prevValue, rewardValue: event.target.value })) } />
{ uniqueRewards
? <div className="flex items-center px-2 rounded bg-muted">
<Text small>Unique</Text>
</div>
: <NitroInput
min={ 0 }
max={ 100 }
placeholder="Chance %"
type="number"
value={ reward.probability }
onChange={ event => updateReward(index, prevValue => ({ ...prevValue, probability: Number(event.target.value) })) } /> }
{ (reward.rewardType === 'points')
?
<NitroInput
min={ 0 }
placeholder={ getExtraFieldPlaceholder(reward.rewardType) }
type="number"
value={ reward.pointsType }
onChange={ event => updateReward(index, prevValue => ({ ...prevValue, pointsType: Number(event.target.value) })) } />
: <div className="flex items-center px-2 rounded bg-muted">
<Text small>{ getExtraFieldLabel(reward.rewardType) }</Text>
</div> }
<div className="flex items-center justify-end">
{ (index > 0) &&
<Button variant="danger" onClick={ event => removeReward(index) }>
<FaTrash className="fa-icon" />
</Button> }
</div>
<NitroInput placeholder="Item Code" type="text" value={ reward.itemCode } onChange={ e => updateReward(index, reward.isBadge, e.target.value, reward.probability) } />
<NitroInput placeholder="Probability" type="number" value={ reward.probability } onChange={ e => updateReward(index, reward.isBadge, reward.itemCode, Number(e.target.value)) } />
{ (index > 0) &&
<Button variant="danger" onClick={ event => removeReward(index) }>
<FaTrash className="fa-icon" />
</Button> }
</div>
);
}) }
</div>
<Text center small className="p-1 rounded bg-muted">
Extra Currency uses Amount as the quantity and Currency Type as the purse type id. Example: amount 200 + type 105.
</Text>
</WiredActionBaseView>
);
};
@@ -28,6 +28,7 @@ import { WiredActionMoveAndRotateFurniView } from './WiredActionMoveAndRotateFur
import { WiredActionMoveFurniToView } from './WiredActionMoveFurniToView';
import { WiredActionMoveFurniView } from './WiredActionMoveFurniView';
import { WiredActionMuteUserView } from './WiredActionMuteUserView';
import { WiredActionRelativeMoveView } from './WiredActionRelativeMoveView';
import { WiredActionResetView } from './WiredActionResetView';
import { WiredActionSetFurniStateToView } from './WiredActionSetFurniStateToView';
import { WiredActionTeleportView } from './WiredActionTeleportView';
@@ -86,6 +87,8 @@ export const WiredActionLayoutView = (code: number) =>
return <WiredActionMoveFurniToView />;
case WiredActionLayoutCode.MUTE_USER:
return <WiredActionMuteUserView />;
case WiredActionLayoutCode.RELATIVE_MOVE:
return <WiredActionRelativeMoveView />;
case WiredActionLayoutCode.RESET:
return <WiredActionResetView />;
case WiredActionLayoutCode.SET_FURNI_STATE:
@@ -0,0 +1,120 @@
import { FC, useEffect, useState } from 'react';
import { FaArrowDown, FaArrowLeft, FaArrowRight, FaArrowUp } from 'react-icons/fa';
import { LocalizeText, WiredFurniType } from '../../../../api';
import { Slider, Text } from '../../../../common';
import { useWired } from '../../../../hooks';
import { WiredSourcesSelector } from '../WiredSourcesSelector';
import { WiredActionBaseView } from './WiredActionBaseView';
const MAX_DISTANCE = 20;
const HORIZONTAL_OPTIONS = [
{ value: 0, icon: <FaArrowLeft /> },
{ value: 1, icon: <FaArrowRight /> }
];
const VERTICAL_OPTIONS = [
{ value: 0, icon: <FaArrowDown /> },
{ value: 1, icon: <FaArrowUp /> }
];
const normalizeDirection = (value: number, fallback = 1) =>
{
if(value === 0 || value === 1) return value;
return fallback;
};
const normalizeDistance = (value: number) =>
{
if(isNaN(value)) return 0;
return Math.max(0, Math.min(MAX_DISTANCE, value));
};
export const WiredActionRelativeMoveView: FC<{}> = () =>
{
const { trigger = null, setIntParams = null } = useWired();
const [horizontalDirection, setHorizontalDirection] = useState(1);
const [horizontalDistance, setHorizontalDistance] = useState(0);
const [verticalDirection, setVerticalDirection] = useState(1);
const [verticalDistance, setVerticalDistance] = useState(0);
const [ furniSource, setFurniSource ] = useState<number>(() =>
{
if(trigger?.intData?.length > 4) return trigger.intData[4];
return (trigger?.selectedItems?.length ?? 0) > 0 ? 100 : 0;
});
useEffect(() =>
{
if(!trigger) return;
setHorizontalDirection((trigger.intData.length > 0) ? normalizeDirection(trigger.intData[0], 1) : 1);
setHorizontalDistance((trigger.intData.length > 1) ? normalizeDistance(trigger.intData[1]) : 0);
setVerticalDirection((trigger.intData.length > 2) ? normalizeDirection(trigger.intData[2], 1) : 1);
setVerticalDistance((trigger.intData.length > 3) ? normalizeDistance(trigger.intData[3]) : 0);
if(trigger.intData.length > 4) setFurniSource(trigger.intData[4]);
else setFurniSource((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0);
}, [ trigger ]);
const save = () => setIntParams([
horizontalDirection,
horizontalDistance,
verticalDirection,
verticalDistance,
furniSource
]);
return (
<WiredActionBaseView
hasSpecialInput={ true }
requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT }
save={ save }
footer={ <WiredSourcesSelector showFurni={ true } furniSource={ furniSource } onChangeFurni={ setFurniSource } /> }>
<div className="flex flex-col gap-2">
<Text bold>{ LocalizeText('wiredfurni.params.movement.horizontal.selection') }</Text>
<div className="flex gap-2">
{ HORIZONTAL_OPTIONS.map(option =>
{
return (
<label key={ option.value } className="flex items-center gap-1">
<input checked={ (horizontalDirection === option.value) } className="form-check-input" name="relativeMoveHorizontal" type="radio" onChange={ () => setHorizontalDirection(option.value) } />
<Text>{ option.icon }</Text>
</label>
);
}) }
</div>
<Text>{ LocalizeText('wiredfurni.params.movement.horizontal.distance', [ 'distance' ], [ horizontalDistance.toString() ]) }</Text>
<Slider
max={ MAX_DISTANCE }
min={ 0 }
step={ 1 }
value={ horizontalDistance }
onChange={ value => setHorizontalDistance(value as number) } />
</div>
<div className="flex flex-col gap-2">
<Text bold>{ LocalizeText('wiredfurni.params.movement.vertical.selection') }</Text>
<div className="flex gap-2">
{ VERTICAL_OPTIONS.map(option =>
{
return (
<label key={ option.value } className="flex items-center gap-1">
<input checked={ (verticalDirection === option.value) } className="form-check-input" name="relativeMoveVertical" type="radio" onChange={ () => setVerticalDirection(option.value) } />
<Text>{ option.icon }</Text>
</label>
);
}) }
</div>
<Text>{ LocalizeText('wiredfurni.params.movement.vertical.distance', [ 'distance' ], [ verticalDistance.toString() ]) }</Text>
<Slider
max={ MAX_DISTANCE }
min={ 0 }
step={ 1 }
value={ verticalDistance }
onChange={ value => setVerticalDistance(value as number) } />
</div>
</WiredActionBaseView>
);
};
@@ -0,0 +1,162 @@
import { FC, useEffect, useMemo, useState } from 'react';
import { LocalizeText, WiredFurniType } from '../../../../api';
import { Slider, Text } from '../../../../common';
import { useWired } from '../../../../hooks';
import { WiredSourcesSelector } from '../WiredSourcesSelector';
import { WiredActionBaseView } from './WiredActionBaseView';
const MIN_ALTITUDE = 0;
const MAX_ALTITUDE = 40;
const ALTITUDE_STEP = 0.01;
const ALTITUDE_PATTERN = /^\d*(\.\d{0,2})?$/;
const clampAltitude = (value: number) =>
{
if(isNaN(value)) return MIN_ALTITUDE;
const clamped = Math.min(MAX_ALTITUDE, Math.max(MIN_ALTITUDE, value));
return parseFloat(clamped.toFixed(2));
};
const formatAltitude = (value: number) =>
{
const normalized = clampAltitude(value);
const text = normalized.toFixed(2);
return text.replace(/\.00$/, '').replace(/(\.\d)0$/, '$1');
};
const parseAltitude = (value: string) =>
{
if(!value || !value.trim().length) return 0;
const parsed = parseFloat(value);
if(isNaN(parsed)) return 0;
return clampAltitude(parsed);
};
const OPERATOR_OPTIONS = [
{ value: 0, label: 'wiredfurni.params.operator.0' },
{ value: 1, label: 'wiredfurni.params.operator.1' },
{ value: 2, label: 'wiredfurni.params.operator.2' }
];
const normalizeOperator = (value: number) =>
{
if(value < 0 || value > 2) return 2;
return value;
};
export const WiredActionSetAltitudeView: FC<{}> = () =>
{
const { trigger = null, setIntParams = null, setStringParam = null } = useWired();
const [ operator, setOperator ] = useState(2);
const [ furniSource, setFurniSource ] = useState<number>(() =>
{
if(trigger?.intData?.length > 1) return trigger.intData[1];
return (trigger?.selectedItems?.length ?? 0) > 0 ? 100 : 0;
});
const [ altitude, setAltitude ] = useState(0);
const [ altitudeInput, setAltitudeInput ] = useState('0');
const normalizedAltitudeText = useMemo(() => formatAltitude(altitude), [ altitude ]);
useEffect(() =>
{
if(!trigger) return;
setOperator((trigger.intData.length > 0) ? normalizeOperator(trigger.intData[0]) : 2);
setFurniSource((trigger.intData.length > 1) ? trigger.intData[1] : ((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0));
const nextAltitude = parseAltitude(trigger.stringData);
setAltitude(nextAltitude);
setAltitudeInput(formatAltitude(nextAltitude));
}, [ trigger ]);
const updateAltitude = (value: number) =>
{
const nextValue = clampAltitude(value);
setAltitude(nextValue);
setAltitudeInput(formatAltitude(nextValue));
};
const updateAltitudeInput = (value: string) =>
{
if(!ALTITUDE_PATTERN.test(value)) return;
setAltitudeInput(value);
if(!value.length)
{
setAltitude(0);
return;
}
const parsedValue = parseFloat(value);
if(isNaN(parsedValue)) return;
if(parsedValue > MAX_ALTITUDE)
{
updateAltitude(MAX_ALTITUDE);
return;
}
setAltitude(clampAltitude(parsedValue));
};
const save = () =>
{
setIntParams([
operator,
furniSource
]);
setStringParam(normalizedAltitudeText);
};
return (
<WiredActionBaseView
hasSpecialInput={ true }
requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_BY_TYPE_OR_FROM_CONTEXT }
save={ save }
footer={ <WiredSourcesSelector showFurni={ true } furniSource={ furniSource } onChangeFurni={ setFurniSource } /> }>
<div className="flex flex-col gap-2">
{ OPERATOR_OPTIONS.map(option =>
{
return (
<div key={ option.value } className="flex items-center gap-1">
<input checked={ (operator === option.value) } className="form-check-input" id={ `setAltitudeOperator${ option.value }` } name="setAltitudeOperator" type="radio" onChange={ () => setOperator(option.value) } />
<Text>{ LocalizeText(option.label) }</Text>
</div>
);
}) }
</div>
<div className="flex flex-col gap-1">
<Text bold>{ LocalizeText('wiredfurni.params.setaltitude') }</Text>
<input
className="form-control form-control-sm"
inputMode="decimal"
type="text"
value={ altitudeInput }
onBlur={ () => setAltitudeInput(formatAltitude(altitude)) }
onChange={ event => updateAltitudeInput(event.target.value) } />
</div>
<div className="flex flex-col gap-1">
<Slider
max={ MAX_ALTITUDE }
min={ MIN_ALTITUDE }
step={ ALTITUDE_STEP }
value={ altitude }
onChange={ event => updateAltitude(event as number) } />
<Text small>{ normalizedAltitudeText }</Text>
</div>
</WiredActionBaseView>
);
};
@@ -0,0 +1,39 @@
import { FC, useEffect, useState } from 'react';
import { LocalizeText, WiredFurniType } from '../../../../api';
import { Text } from '../../../../common';
import { useWired } from '../../../../hooks';
import { NitroInput } from '../../../../layout';
import { WiredTriggerBaseView } from './WiredTriggerBaseView';
export const WiredTriggerAvatarLeaveRoomView: FC<{}> = props =>
{
const [ username, setUsername ] = useState('');
const [ avatarMode, setAvatarMode ] = useState(0);
const { trigger = null, setStringParam = null } = useWired();
const save = () => setStringParam((avatarMode === 1) ? username : '');
useEffect(() =>
{
setUsername(trigger.stringData);
setAvatarMode(trigger.stringData ? 1 : 0);
}, [ trigger ]);
return (
<WiredTriggerBaseView hasSpecialInput={ true } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } save={ save }>
<div className="flex flex-col gap-1">
<Text bold>{ LocalizeText('wiredfurni.params.picktriggerer') }</Text>
<div className="flex items-center gap-1">
<input checked={ (avatarMode === 0) } className="form-check-input" id="avatarMode0" name="avatarMode" type="radio" onChange={ event => setAvatarMode(0) } />
<Text>{ LocalizeText('wiredfurni.params.anyavatar') }</Text>
</div>
<div className="flex items-center gap-1">
<input checked={ (avatarMode === 1) } className="form-check-input" id="avatarMode1" name="avatarMode" type="radio" onChange={ event => setAvatarMode(1) } />
<Text>{ LocalizeText('wiredfurni.params.certainavatar') }</Text>
</div>
{ (avatarMode === 1) &&
<NitroInput type="text" value={ username } onChange={ event => setUsername(event.target.value) } /> }
</div>
</WiredTriggerBaseView>
);
};
@@ -0,0 +1,8 @@
import { FC } from 'react';
import { WiredFurniType } from '../../../../api';
import { WiredTriggerBaseView } from './WiredTriggerBaseView';
export const WiredTriggerClickFurniView: FC<{}> = () =>
{
return <WiredTriggerBaseView hasSpecialInput={ false } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_OR_BY_TYPE } save={ null } />;
};
@@ -0,0 +1,20 @@
import { FC, useEffect } from 'react';
import { WiredFurniType } from '../../../../api';
import { useWired } from '../../../../hooks';
import { WiredTriggerBaseView } from './WiredTriggerBaseView';
const CLICK_TILE_INTERACTION_TYPES = [ 'room_invisible_click_tile' ];
export const WiredTriggerClickTileView: FC<{}> = () =>
{
const { setAllowedInteractionTypes } = useWired();
useEffect(() =>
{
setAllowedInteractionTypes(CLICK_TILE_INTERACTION_TYPES);
return () => setAllowedInteractionTypes(null);
}, [ setAllowedInteractionTypes ]);
return <WiredTriggerBaseView hasSpecialInput={ false } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_OR_BY_TYPE } save={ null } />;
};
@@ -0,0 +1,8 @@
import { FC } from 'react';
import { WiredFurniType } from '../../../../api';
import { WiredTriggerBaseView } from './WiredTriggerBaseView';
export const WiredTriggerClickUserView: FC<{}> = () =>
{
return <WiredTriggerBaseView hasSpecialInput={ false } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } save={ null } />;
};
@@ -0,0 +1,32 @@
import { FC, useEffect, useState } from 'react';
import { LocalizeText, WiredFurniType } from '../../../../api';
import { Slider, Text } from '../../../../common';
import { useWired } from '../../../../hooks';
import { WiredTriggerBaseView } from './WiredTriggerBaseView';
export const WiredTriggeExecutePeriodicallyShortView: FC<{}> = () =>
{
const [ time, setTime ] = useState(10);
const { trigger = null, setIntParams = null } = useWired();
const save = () => setIntParams([ time ]);
useEffect(() =>
{
setTime((trigger.intData.length > 0) ? trigger.intData[0] : 10);
}, [ trigger ]);
return (
<WiredTriggerBaseView hasSpecialInput={ true } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } save={ save }>
<div className="flex flex-col gap-1">
<Text bold>{ LocalizeText('wiredfurni.params.settime', [ 'seconds' ], [ ((time * 50) / 1000).toFixed(2) ]) }</Text>
<Text small>{ `${ time * 50 } ms` }</Text>
<Slider
max={ 10 }
min={ 1 }
value={ time }
onChange={ event => setTime(event) } />
</div>
</WiredTriggerBaseView>
);
};
@@ -1,14 +1,20 @@
import { WiredTriggerLayout } from '../../../../api';
import { WiredTriggerAvatarEnterRoomView } from './WiredTriggerAvatarEnterRoomView';
import { WiredTriggerAvatarLeaveRoomView } from './WiredTriggerAvatarLeaveRoomView';
import { WiredTriggerAvatarSaysSomethingView } from './WiredTriggerAvatarSaysSomethingView';
import { WiredTriggerAvatarWalksOffFurniView } from './WiredTriggerAvatarWalksOffFurniView';
import { WiredTriggerAvatarWalksOnFurniView } from './WiredTriggerAvatarWalksOnFurni';
import { WiredTriggerBotReachedAvatarView } from './WiredTriggerBotReachedAvatarView';
import { WiredTriggerBotReachedStuffView } from './WiredTriggerBotReachedStuffView';
import { WiredTriggerClickFurniView } from './WiredTriggerClickFurniView';
import { WiredTriggerClickTileView } from './WiredTriggerClickTileView';
import { WiredTriggerClickUserView } from './WiredTriggerClickUserView';
import { WiredTriggerCollisionView } from './WiredTriggerCollisionView';
import { WiredTriggerUserPerformsActionView } from './WiredTriggerUserPerformsActionView';
import { WiredTriggeExecuteOnceView } from './WiredTriggerExecuteOnceView';
import { WiredTriggeExecutePeriodicallyLongView } from './WiredTriggerExecutePeriodicallyLongView';
import { WiredTriggeExecutePeriodicallyView } from './WiredTriggerExecutePeriodicallyView';
import { WiredTriggeExecutePeriodicallyShortView } from './WiredTriggerExecutePeriodicallyShortView';
import { WiredTriggerGameEndsView } from './WiredTriggerGameEndsView';
import { WiredTriggerGameStartsView } from './WiredTriggerGameStartsView';
import { WiredTriggeScoreAchievedView } from './WiredTriggerScoreAchievedView';
@@ -21,6 +27,8 @@ export const WiredTriggerLayoutView = (code: number) =>
{
case WiredTriggerLayout.AVATAR_ENTERS_ROOM:
return <WiredTriggerAvatarEnterRoomView />;
case WiredTriggerLayout.AVATAR_LEAVES_ROOM:
return <WiredTriggerAvatarLeaveRoomView />;
case WiredTriggerLayout.AVATAR_SAYS_SOMETHING:
return <WiredTriggerAvatarSaysSomethingView />;
case WiredTriggerLayout.AVATAR_WALKS_OFF_FURNI:
@@ -31,12 +39,22 @@ export const WiredTriggerLayoutView = (code: number) =>
return <WiredTriggerBotReachedAvatarView />;
case WiredTriggerLayout.BOT_REACHED_STUFF:
return <WiredTriggerBotReachedStuffView />;
case WiredTriggerLayout.CLICK_FURNI:
return <WiredTriggerClickFurniView />;
case WiredTriggerLayout.CLICK_TILE:
return <WiredTriggerClickTileView />;
case WiredTriggerLayout.CLICK_USER:
return <WiredTriggerClickUserView />;
case WiredTriggerLayout.USER_PERFORMS_ACTION:
return <WiredTriggerUserPerformsActionView />;
case WiredTriggerLayout.COLLISION:
return <WiredTriggerCollisionView />;
case WiredTriggerLayout.EXECUTE_ONCE:
return <WiredTriggeExecuteOnceView />;
case WiredTriggerLayout.EXECUTE_PERIODICALLY:
return <WiredTriggeExecutePeriodicallyView />;
case WiredTriggerLayout.EXECUTE_PERIODICALLY_SHORT:
return <WiredTriggeExecutePeriodicallyShortView />;
case WiredTriggerLayout.EXECUTE_PERIODICALLY_LONG:
return <WiredTriggeExecutePeriodicallyLongView />;
case WiredTriggerLayout.GAME_ENDS:
@@ -1,8 +1,34 @@
import { FC } from 'react';
import { WiredFurniType } from '../../../../api';
import { FC, useEffect, useState } from 'react';
import { LocalizeText, WiredFurniType } from '../../../../api';
import { Text } from '../../../../common';
import { useWired } from '../../../../hooks';
import { WiredTriggerBaseView } from './WiredTriggerBaseView';
export const WiredTriggerToggleFurniView: FC<{}> = props =>
export const WiredTriggerToggleFurniView: FC<{}> = () =>
{
return <WiredTriggerBaseView hasSpecialInput={ false } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID_OR_BY_TYPE } save={ null } />;
const [ triggerMode, setTriggerMode ] = useState(0);
const { trigger = null, setIntParams = null } = useWired();
const save = () => setIntParams([ triggerMode ]);
useEffect(() =>
{
setTriggerMode((trigger?.intData?.length > 0) ? trigger.intData[0] : 0);
}, [ trigger ]);
return (
<WiredTriggerBaseView hasSpecialInput={ true } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_BY_ID } save={ save }>
<div className="flex flex-col gap-1">
<Text bold>{ LocalizeText('wiredfurni.params.condition.state') }</Text>
<div className="flex items-center gap-1">
<input checked={ (triggerMode === 1) } className="form-check-input" id="stateTrigger1" name="stateTrigger" type="radio" onChange={ () => setTriggerMode(1) } />
<Text>{ LocalizeText('wiredfurni.params.state_trigger.1') }</Text>
</div>
<div className="flex items-center gap-1">
<input checked={ (triggerMode === 0) } className="form-check-input" id="stateTrigger0" name="stateTrigger" type="radio" onChange={ () => setTriggerMode(0) } />
<Text>{ LocalizeText('wiredfurni.params.state_trigger.0') }</Text>
</div>
</div>
</WiredTriggerBaseView>
);
};
@@ -0,0 +1,103 @@
import { FC, useEffect, useState } from 'react';
import { LocalizeText, WiredFurniType } from '../../../../api';
import { Text } from '../../../../common';
import { useWired } from '../../../../hooks';
import { WiredTriggerBaseView } from './WiredTriggerBaseView';
const ACTION_WAVE = 1;
const ACTION_BLOW_KISS = 2;
const ACTION_LAUGH = 3;
const ACTION_AWAKE = 4;
const ACTION_RELAX = 5;
const ACTION_SIT = 6;
const ACTION_STAND = 7;
const ACTION_LAY = 8;
const ACTION_SIGN = 9;
const ACTION_DANCE = 10;
const ACTION_THUMB_UP = 11;
const ACTION_OPTIONS = [
{ value: ACTION_WAVE, label: 'widget.memenu.wave' },
{ value: ACTION_BLOW_KISS, label: 'widget.memenu.blow' },
{ value: ACTION_LAUGH, label: 'widget.memenu.laugh' },
{ value: ACTION_THUMB_UP, label: 'widget.memenu.thumb' },
{ value: ACTION_AWAKE, label: 'wiredfurni.params.action.4' },
{ value: ACTION_RELAX, label: 'avatar.widget.random_walk' },
{ value: ACTION_SIT, label: 'widget.memenu.sit' },
{ value: ACTION_STAND, label: 'widget.memenu.stand' },
{ value: ACTION_LAY, label: 'wiredfurni.params.action.8' },
{ value: ACTION_SIGN, label: 'widget.memenu.sign' },
{ value: ACTION_DANCE, label: 'widget.memenu.dance' }
];
const SIGN_OPTIONS = Array.from({ length: 18 }, (_, value) => ({
value,
label: `wiredfurni.params.action.sign.${ value }`
}));
const DANCE_OPTIONS = [
{ value: 1, label: 'widget.memenu.dance1' },
{ value: 2, label: 'widget.memenu.dance2' },
{ value: 3, label: 'widget.memenu.dance3' },
{ value: 4, label: 'widget.memenu.dance4' }
];
export const WiredTriggerUserPerformsActionView: FC<{}> = () =>
{
const [ selectedAction, setSelectedAction ] = useState(ACTION_WAVE);
const [ signFilterEnabled, setSignFilterEnabled ] = useState(false);
const [ signId, setSignId ] = useState(0);
const [ danceFilterEnabled, setDanceFilterEnabled ] = useState(false);
const [ danceId, setDanceId ] = useState(1);
const { trigger = null, setIntParams = null } = useWired();
const save = () => setIntParams([
selectedAction,
signFilterEnabled ? 1 : 0,
signId,
danceFilterEnabled ? 1 : 0,
danceId
]);
useEffect(() =>
{
setSelectedAction((trigger?.intData?.length > 0) ? trigger.intData[0] : ACTION_WAVE);
setSignFilterEnabled((trigger?.intData?.length > 1) ? (trigger.intData[1] === 1) : false);
setSignId((trigger?.intData?.length > 2) ? trigger.intData[2] : 0);
setDanceFilterEnabled((trigger?.intData?.length > 3) ? (trigger.intData[3] === 1) : false);
setDanceId((trigger?.intData?.length > 4) ? trigger.intData[4] : 1);
}, [ trigger ]);
return (
<WiredTriggerBaseView hasSpecialInput={ true } requiresFurni={ WiredFurniType.STUFF_SELECTION_OPTION_NONE } save={ save }>
<div className="flex flex-col gap-1">
<Text bold>Action</Text>
<select className="form-select form-select-sm" value={ selectedAction } onChange={ event => setSelectedAction(parseInt(event.target.value)) }>
{ ACTION_OPTIONS.map(option => <option key={ option.value } value={ option.value }>{ LocalizeText(option.label) }</option>) }
</select>
</div>
{ (selectedAction === ACTION_SIGN) &&
<div className="flex flex-col gap-1">
<div className="flex items-center gap-1">
<input checked={ signFilterEnabled } className="form-check-input" id="signFilterEnabled" type="checkbox" onChange={ event => setSignFilterEnabled(event.target.checked) } />
<Text>{ LocalizeText('wiredfurni.params.sign_filter') }</Text>
</div>
{ signFilterEnabled &&
<select className="form-select form-select-sm" value={ signId } onChange={ event => setSignId(parseInt(event.target.value)) }>
{ SIGN_OPTIONS.map(option => <option key={ option.value } value={ option.value }>{ LocalizeText(option.label) }</option>) }
</select> }
</div> }
{ (selectedAction === ACTION_DANCE) &&
<div className="flex flex-col gap-1">
<div className="flex items-center gap-1">
<input checked={ danceFilterEnabled } className="form-check-input" id="danceFilterEnabled" type="checkbox" onChange={ event => setDanceFilterEnabled(event.target.checked) } />
<Text>{ LocalizeText('wiredfurni.params.dance_filter') }</Text>
</div>
{ danceFilterEnabled &&
<select className="form-select form-select-sm" value={ danceId } onChange={ event => setDanceId(parseInt(event.target.value)) }>
{ DANCE_OPTIONS.map(option => <option key={ option.value } value={ option.value }>{ LocalizeText(option.label) }</option>) }
</select> }
</div> }
</WiredTriggerBaseView>
);
};