From 4a0661181cba333de953be343f16704644b6d693 Mon Sep 17 00:00:00 2001 From: Lorenzune Date: Thu, 19 Mar 2026 14:27:33 +0100 Subject: [PATCH] feat(wired-ui): add advanced condition editors --- public/UITexts.example | 76 ++++++- src/api/wired/WiredConditionLayoutCode.ts | 10 + .../WiredConditionHasAltitudeView.tsx | 185 +++++++++++++++++ .../conditions/WiredConditionLayoutView.tsx | 25 +++ .../WiredConditionMatchDateView.tsx | 195 ++++++++++++++++++ .../WiredConditionMatchTimeView.tsx | 163 +++++++++++++++ .../WiredConditionTeamHasRankView.tsx | 102 +++++++++ .../WiredConditionTeamHasScoreView.tsx | 158 ++++++++++++++ .../WiredConditionTriggererMatchView.tsx | 130 ++++++++++++ .../WiredConditionUserPerformsActionView.tsx | 151 ++++++++++++++ 10 files changed, 1187 insertions(+), 8 deletions(-) create mode 100644 src/components/wired/views/conditions/WiredConditionHasAltitudeView.tsx create mode 100644 src/components/wired/views/conditions/WiredConditionMatchDateView.tsx create mode 100644 src/components/wired/views/conditions/WiredConditionMatchTimeView.tsx create mode 100644 src/components/wired/views/conditions/WiredConditionTeamHasRankView.tsx create mode 100644 src/components/wired/views/conditions/WiredConditionTeamHasScoreView.tsx create mode 100644 src/components/wired/views/conditions/WiredConditionTriggererMatchView.tsx create mode 100644 src/components/wired/views/conditions/WiredConditionUserPerformsActionView.tsx diff --git a/public/UITexts.example b/public/UITexts.example index f4c6168..980458f 100644 --- a/public/UITexts.example +++ b/public/UITexts.example @@ -13,12 +13,72 @@ "widget.settings.interface.fps.warning": "Het zetten van FPS naar unlimited kan prestatie problemen veroorzaken!", "widget.settings.interface.secondary": "Verander de window header kleur", "widget.settings.interface.reset": "Reset header kleur naar default", - "widget.room.chat.hide_pets": "Verberg dieren", - "widget.room.chat.hide_avatars": "Verberg avatars", - "widget.room.chat.hide_balloon": "Verberg Spreekballon", - "widget.room.chat.show_balloon": "Spreekballon", - "widget.room.chat.clear_history": "leeg geschiedenis", - "widget.room.youtube.shared": "YouTube word gedeeld", - "widget.room.youtube.open_video": "Open de video", - "wiredfurni.params.area_selection.selected": "Geselecteerd gebied: Lengte=%x%, Breedte=%y%, breedte=%w%, hoogte=%h%" + "widget.room.chat.hide_pets": "Verberg dieren", + "widget.room.chat.hide_avatars": "Verberg avatars", + "widget.room.chat.hide_balloon": "Verberg Spreekballon", + "widget.room.chat.show_balloon": "Spreekballon", + "widget.room.chat.clear_history": "leeg geschiedenis", + "widget.room.youtube.shared": "YouTube word gedeeld", + "widget.room.youtube.open_video": "Open de video", + "wiredfurni.params.area_selection.selected": "Geselecteerd gebied: Lengte=%x%, Breedte=%y%, breedte=%w%, hoogte=%h%", + "wiredfurni.params.sources.collapse": "Nascondi le impostazioni avanzate", + "wiredfurni.params.sources.expand": "Mostra le impostazioni avanzate", + "wiredfurni.params.quantifier_selection": "Abbina condizione se:", + "wiredfurni.params.quantifier.users.0": "Tutti gli utenti corrispondono", + "wiredfurni.params.quantifier.users.1": "Uno qualsiasi degli utenti corrisponde", + "wiredfurni.params.quantifier.users.neg.0": "Uno qualsiasi degli utenti non corrisponde", + "wiredfurni.params.quantifier.users.neg.1": "Nessuno degli utenti corrisponde", + "wiredfurni.params.quantifier.furni.0": "Tutti i Furni corrispondono", + "wiredfurni.params.quantifier.furni.1": "Uno qualsiasi dei Furni corrisponde", + "wiredfurni.params.usertype.1": "Habbo", + "wiredfurni.params.usertype.2": "Cucciolo", + "wiredfurni.params.usertype.4": "Bot", + "wiredfurni.params.sources.users.title.match.0": "Gli utenti da abbinare:", + "wiredfurni.params.sources.users.title.match.1": "Utenti da comparare con:", + "wiredfurni.params.sources.users.101": "Usa l'utente specificato dal nome", + "wiredfurni.params.comparison.0": "Più basso di", + "wiredfurni.params.comparison.1": "È uguale a", + "wiredfurni.params.comparison.2": "Più alto di", + "wiredfurni.params.team": "Scegli una squadra", + "wiredfurni.params.team.1": "Rossa", + "wiredfurni.params.team.2": "Verde", + "wiredfurni.params.team.3": "Blu", + "wiredfurni.params.team.4": "Gialla", + "wiredfurni.params.team.triggerer": "Squadra dell'innescatore", + "wiredfurni.params.comparison_selection": "Scegli tipo:", + "wiredfurni.params.setscore2": "La squadra deve segnare:", + "wiredfurni.params.placement_selection": "Posizione:", + "wiredfurni.params.placement.1": "1.", + "wiredfurni.params.placement.2": "2.", + "wiredfurni.params.placement.3": "3.", + "wiredfurni.params.placement.4": "4.", + "wiredfurni.params.time.hour_selection": "Ore:", + "wiredfurni.params.time.minute_selection": "Minuti:", + "wiredfurni.params.time.second_selection": "Secondi:", + "wiredfurni.params.time.skip": "Non usare il filtro", + "wiredfurni.params.time.exact": "Esatto", + "wiredfurni.params.time.range": "Range", + "wiredfurni.params.time.weekday_selection": "Giorno della settimana:", + "wiredfurni.params.time.weekday.1": "Lunedì", + "wiredfurni.params.time.weekday.2": "Martedì", + "wiredfurni.params.time.weekday.3": "Mercoledì", + "wiredfurni.params.time.weekday.4": "Giovedì", + "wiredfurni.params.time.weekday.5": "Venerdì", + "wiredfurni.params.time.weekday.6": "Sabato", + "wiredfurni.params.time.weekday.7": "Domenica", + "wiredfurni.params.time.day_selection": "Giorno:", + "wiredfurni.params.time.month_selection": "Mese:", + "wiredfurni.params.time.month.10": "Ott.", + "wiredfurni.params.time.month.11": "Nov.", + "wiredfurni.params.time.month.12": "Dic.", + "wiredfurni.params.time.month.1": "Gen.", + "wiredfurni.params.time.month.2": "Feb.", + "wiredfurni.params.time.month.3": "Mar.", + "wiredfurni.params.time.month.4": "Apr.", + "wiredfurni.params.time.month.5": "Mag.", + "wiredfurni.params.time.month.6": "Giu.", + "wiredfurni.params.time.month.7": "Lug.", + "wiredfurni.params.time.month.8": "Ago.", + "wiredfurni.params.time.month.9": "Set.", + "wiredfurni.params.time.year_selection": "Anno:" } diff --git a/src/api/wired/WiredConditionLayoutCode.ts b/src/api/wired/WiredConditionLayoutCode.ts index d63b3e1..b344f8b 100644 --- a/src/api/wired/WiredConditionLayoutCode.ts +++ b/src/api/wired/WiredConditionLayoutCode.ts @@ -27,4 +27,14 @@ export class WiredConditionlayout public static DATE_RANGE_ACTIVE: number = 24; public static ACTOR_HAS_HANDITEM: number = 25; public static COUNTER_TIME_MATCHES: number = 27; + public static USER_PERFORMS_ACTION: number = 28; + public static HAS_ALTITUDE: number = 29; + public static NOT_USER_PERFORMS_ACTION: number = 30; + public static NOT_ACTOR_HAS_HANDITEM: number = 31; + public static TRIGGERER_MATCH: number = 32; + public static NOT_TRIGGERER_MATCH: number = 33; + public static TEAM_HAS_SCORE: number = 34; + public static TEAM_HAS_RANK: number = 35; + public static MATCH_TIME: number = 36; + public static MATCH_DATE: number = 37; } diff --git a/src/components/wired/views/conditions/WiredConditionHasAltitudeView.tsx b/src/components/wired/views/conditions/WiredConditionHasAltitudeView.tsx new file mode 100644 index 0000000..91a6a82 --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionHasAltitudeView.tsx @@ -0,0 +1,185 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Slider, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredSourcesSelector } from '../WiredSourcesSelector'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +const COUNTER_INTERACTION_TYPES = [ 'game_upcounter' ]; +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); +}; + +export const WiredConditionHasAltitudeView: FC<{}> = () => +{ + const { trigger = null, setIntParams = null, setStringParam = null, setAllowedInteractionTypes = null, setAllowedInteractionErrorKey = null } = useWired(); + const [ comparison, setComparison ] = useState(1); + const [ furniSource, setFurniSource ] = useState(() => + { + if(trigger?.intData?.length > 1) return trigger.intData[1]; + return (trigger?.selectedItems?.length ?? 0) > 0 ? 100 : 0; + }); + const [ quantifier, setQuantifier ] = useState(0); + const [ showAdvanced, setShowAdvanced ] = useState(false); + const [ altitude, setAltitude ] = useState(0); + const [ altitudeInput, setAltitudeInput ] = useState('0'); + + useEffect(() => + { + setAllowedInteractionTypes(COUNTER_INTERACTION_TYPES); + setAllowedInteractionErrorKey('wiredfurni.error.require_counter_furni'); + + return () => + { + setAllowedInteractionTypes(null); + setAllowedInteractionErrorKey(null); + }; + }, [ setAllowedInteractionErrorKey, setAllowedInteractionTypes ]); + + useEffect(() => + { + if(!trigger) return; + + setComparison((trigger.intData.length > 0) ? trigger.intData[0] : 1); + setFurniSource((trigger.intData.length > 1) ? trigger.intData[1] : ((trigger.selectedItems?.length ?? 0) > 0 ? 100 : 0)); + setQuantifier((trigger.intData.length > 2) ? trigger.intData[2] : 0); + setShowAdvanced((trigger.intData.length > 1) ? (trigger.intData[1] !== 0 || trigger.intData[2] !== 0) : false); + + 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([ + comparison, + furniSource, + quantifier + ]); + setStringParam(formatAltitude(altitude)); + }; + + return ( + + + { showAdvanced && + <> +
+ { LocalizeText('wiredfurni.params.quantifier_selection') } + { [ 0, 1 ].map(value => + { + return ( +
+ setQuantifier(value) } /> + { LocalizeText(`wiredfurni.params.quantifier.furni.${ value }`) } +
+ ); + }) } +
+ + } + + }> +
+ { [ 0, 1, 2 ].map(value => + { + return ( +
+ setComparison(value) } /> + { LocalizeText(`wiredfurni.params.comparison.${ value }`) } +
+ ); + }) } +
+
+ { LocalizeText('wiredfurni.params.setaltitude') } + setAltitudeInput(formatAltitude(altitude)) } + onChange={ event => updateAltitudeInput(event.target.value) } /> +
+
+ updateAltitude(event as number) } /> + { formatAltitude(altitude) } +
+
+ ); +}; diff --git a/src/components/wired/views/conditions/WiredConditionLayoutView.tsx b/src/components/wired/views/conditions/WiredConditionLayoutView.tsx index 11c2a30..888df16 100644 --- a/src/components/wired/views/conditions/WiredConditionLayoutView.tsx +++ b/src/components/wired/views/conditions/WiredConditionLayoutView.tsx @@ -7,6 +7,9 @@ import { WiredConditionActorIsWearingBadgeView } from './WiredConditionActorIsWe import { WiredConditionActorIsWearingEffectView } from './WiredConditionActorIsWearingEffectView'; import { WiredConditionCounterTimeMatchesView } from './WiredConditionCounterTimeMatchesView'; import { WiredConditionDateRangeView } from './WiredConditionDateRangeView'; +import { WiredConditionMatchDateView } from './WiredConditionMatchDateView'; +import { WiredConditionMatchTimeView } from './WiredConditionMatchTimeView'; +import { WiredConditionHasAltitudeView } from './WiredConditionHasAltitudeView'; import { WiredConditionFurniHasAvatarOnView } from './WiredConditionFurniHasAvatarOnView'; import { WiredConditionFurniHasFurniOnView } from './WiredConditionFurniHasFurniOnView'; import { WiredConditionFurniHasNotFurniOnView } from './WiredConditionFurniHasNotFurniOnView'; @@ -14,6 +17,10 @@ import { WiredConditionFurniIsOfTypeView } from './WiredConditionFurniIsOfTypeVi import { WiredConditionFurniMatchesSnapshotView } from './WiredConditionFurniMatchesSnapshotView'; import { WiredConditionTimeElapsedLessView } from './WiredConditionTimeElapsedLessView'; import { WiredConditionTimeElapsedMoreView } from './WiredConditionTimeElapsedMoreView'; +import { WiredConditionTeamHasRankView } from './WiredConditionTeamHasRankView'; +import { WiredConditionTeamHasScoreView } from './WiredConditionTeamHasScoreView'; +import { WiredConditionTriggererMatchView } from './WiredConditionTriggererMatchView'; +import { WiredConditionUserPerformsActionView } from './WiredConditionUserPerformsActionView'; import { WiredConditionUserCountInRoomView } from './WiredConditionUserCountInRoomView'; export const WiredConditionLayoutView = (code: number) => @@ -21,7 +28,11 @@ export const WiredConditionLayoutView = (code: number) => switch(code) { case WiredConditionlayout.ACTOR_HAS_HANDITEM: + case WiredConditionlayout.NOT_ACTOR_HAS_HANDITEM: return ; + case WiredConditionlayout.TRIGGERER_MATCH: + case WiredConditionlayout.NOT_TRIGGERER_MATCH: + return ; case WiredConditionlayout.ACTOR_IS_GROUP_MEMBER: case WiredConditionlayout.NOT_ACTOR_IN_GROUP: return ; @@ -39,6 +50,10 @@ export const WiredConditionLayoutView = (code: number) => return ; case WiredConditionlayout.DATE_RANGE_ACTIVE: return ; + case WiredConditionlayout.MATCH_TIME: + return ; + case WiredConditionlayout.MATCH_DATE: + return ; case WiredConditionlayout.FURNIS_HAVE_AVATARS: case WiredConditionlayout.FURNI_NOT_HAVE_HABBO: return ; @@ -61,6 +76,16 @@ export const WiredConditionLayoutView = (code: number) => return ; case WiredConditionlayout.COUNTER_TIME_MATCHES: return ; + case WiredConditionlayout.USER_PERFORMS_ACTION: + return ; + case WiredConditionlayout.NOT_USER_PERFORMS_ACTION: + return ; + case WiredConditionlayout.HAS_ALTITUDE: + return ; + case WiredConditionlayout.TEAM_HAS_SCORE: + return ; + case WiredConditionlayout.TEAM_HAS_RANK: + return ; } return null; diff --git a/src/components/wired/views/conditions/WiredConditionMatchDateView.tsx b/src/components/wired/views/conditions/WiredConditionMatchDateView.tsx new file mode 100644 index 0000000..f1b9ccc --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionMatchDateView.tsx @@ -0,0 +1,195 @@ +import { ChangeEvent, FC, useEffect, useMemo, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +const MODE_SKIP = 0; +const MODE_EXACT = 1; +const MODE_RANGE = 2; +const MODE_OPTIONS = [ MODE_SKIP, MODE_EXACT, MODE_RANGE ]; +const WEEKDAY_OPTIONS = [ 1, 2, 3, 4, 5, 6, 7 ]; +const MONTH_OPTIONS = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ]; + +const createMask = (values: number[]) => values.reduce((mask, value) => (mask | (1 << value)), 0); +const ALL_WEEKDAYS_MASK = createMask(WEEKDAY_OPTIONS); +const ALL_MONTHS_MASK = createMask(MONTH_OPTIONS); + +const clampValue = (value: number, min: number, max: number) => +{ + if(isNaN(value)) return min; + + return Math.max(min, Math.min(max, Math.floor(value))); +}; + +const parseInputValue = (event: ChangeEvent, min: number, max: number) => +{ + return clampValue(parseInt(event.target.value || min.toString(), 10), min, max); +}; + +const toggleMaskValue = (mask: number, value: number, enabled: boolean) => +{ + if(enabled) return (mask | (1 << value)); + + return (mask & ~(1 << value)); +}; + +const InlineNumberInput: FC<{ value: number; min: number; max: number; onChange: (value: number) => void }> = props => +{ + const { value = 0, min = 0, max = 0, onChange = null } = props; + + return ( + onChange(parseInputValue(event, min, max)) } /> + ); +}; + +interface MatchDateSectionProps +{ + sectionId: string; + titleKey: string; + mode: number; + fromValue: number; + toValue: number; + min: number; + max: number; + onModeChange: (value: number) => void; + onFromChange: (value: number) => void; + onToChange: (value: number) => void; +} + +const MatchDateSection: FC = props => +{ + const { sectionId = '', titleKey = '', mode = MODE_SKIP, fromValue = 0, toValue = 0, min = 0, max = 0, onModeChange = null, onFromChange = null, onToChange = null } = props; + + return ( +
+ { LocalizeText(titleKey) } +
+ onModeChange(MODE_SKIP) } /> + { LocalizeText('wiredfurni.params.time.skip') } +
+
+ onModeChange(MODE_EXACT) } /> + { LocalizeText('wiredfurni.params.time.exact') } + +
+
+ onModeChange(MODE_RANGE) } /> + { LocalizeText('wiredfurni.params.time.range') } + + - + +
+
+ ); +}; + +export const WiredConditionMatchDateView: FC<{}> = () => +{ + const { trigger = null, setIntParams = null } = useWired(); + const currentYear = useMemo(() => new Date().getFullYear(), []); + const [ weekdayMask, setWeekdayMask ] = useState(ALL_WEEKDAYS_MASK); + const [ dayMode, setDayMode ] = useState(MODE_SKIP); + const [ dayFrom, setDayFrom ] = useState(1); + const [ dayTo, setDayTo ] = useState(31); + const [ monthMask, setMonthMask ] = useState(ALL_MONTHS_MASK); + const [ yearMode, setYearMode ] = useState(MODE_SKIP); + const [ yearFrom, setYearFrom ] = useState(currentYear); + const [ yearTo, setYearTo ] = useState(currentYear); + + useEffect(() => + { + if(!trigger) return; + + setWeekdayMask((trigger.intData[0] && (trigger.intData[0] > 0)) ? trigger.intData[0] : ALL_WEEKDAYS_MASK); + setDayMode(MODE_OPTIONS.includes(trigger.intData[1]) ? trigger.intData[1] : MODE_SKIP); + setDayFrom(clampValue(trigger.intData[2] ?? 1, 1, 31)); + setDayTo(clampValue(trigger.intData[3] ?? 31, 1, 31)); + setMonthMask((trigger.intData[4] && (trigger.intData[4] > 0)) ? trigger.intData[4] : ALL_MONTHS_MASK); + setYearMode(MODE_OPTIONS.includes(trigger.intData[5]) ? trigger.intData[5] : MODE_SKIP); + setYearFrom(clampValue(trigger.intData[6] ?? currentYear, 1, 9999)); + setYearTo(clampValue(trigger.intData[7] ?? currentYear, 1, 9999)); + }, [ currentYear, trigger ]); + + const save = () => + { + setIntParams([ + weekdayMask || ALL_WEEKDAYS_MASK, + dayMode, + clampValue(dayFrom, 1, 31), + clampValue(dayTo, 1, 31), + monthMask || ALL_MONTHS_MASK, + yearMode, + clampValue(yearFrom, 1, 9999), + clampValue(yearTo, 1, 9999) + ]); + }; + + return ( + +
+
+ { LocalizeText('wiredfurni.params.time.weekday_selection') } +
+ { WEEKDAY_OPTIONS.map(value => + { + const checked = ((weekdayMask & (1 << value)) !== 0); + + return ( + + ); + }) } +
+
+ setDayFrom(clampValue(value, 1, 31)) } + onModeChange={ setDayMode } + onToChange={ value => setDayTo(clampValue(value, 1, 31)) } /> +
+ { LocalizeText('wiredfurni.params.time.month_selection') } +
+ { MONTH_OPTIONS.map(value => + { + const checked = ((monthMask & (1 << value)) !== 0); + + return ( + + ); + }) } +
+
+ setYearFrom(clampValue(value, 1, 9999)) } + onModeChange={ setYearMode } + onToChange={ value => setYearTo(clampValue(value, 1, 9999)) } /> +
+
+ ); +}; diff --git a/src/components/wired/views/conditions/WiredConditionMatchTimeView.tsx b/src/components/wired/views/conditions/WiredConditionMatchTimeView.tsx new file mode 100644 index 0000000..426ab99 --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionMatchTimeView.tsx @@ -0,0 +1,163 @@ +import { ChangeEvent, FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +const MODE_SKIP = 0; +const MODE_EXACT = 1; +const MODE_RANGE = 2; +const MODE_OPTIONS = [ MODE_SKIP, MODE_EXACT, MODE_RANGE ]; + +const clampValue = (value: number, min: number, max: number) => +{ + if(isNaN(value)) return min; + + return Math.max(min, Math.min(max, Math.floor(value))); +}; + +interface TimeFilterSectionProps +{ + sectionId: string; + titleKey: string; + min: number; + max: number; + mode: number; + fromValue: number; + toValue: number; + onModeChange: (value: number) => void; + onFromChange: (value: number) => void; + onToChange: (value: number) => void; +} + +const parseInputValue = (event: ChangeEvent, min: number, max: number) => +{ + return clampValue(parseInt(event.target.value || min.toString(), 10), min, max); +}; + +const InlineNumberInput: FC<{ value: number; min: number; max: number; onChange: (value: number) => void }> = props => +{ + const { value = 0, min = 0, max = 0, onChange = null } = props; + + return ( + onChange(parseInputValue(event, min, max)) } /> + ); +}; + +const TimeFilterSection: FC = props => +{ + const { sectionId = '', titleKey = '', min = 0, max = 0, mode = MODE_SKIP, fromValue = 0, toValue = 0, onModeChange = null, onFromChange = null, onToChange = null } = props; + + return ( +
+ { LocalizeText(titleKey) } +
+ onModeChange(MODE_SKIP) } /> + { LocalizeText('wiredfurni.params.time.skip') } +
+
+ onModeChange(MODE_EXACT) } /> + { LocalizeText('wiredfurni.params.time.exact') } + +
+
+ onModeChange(MODE_RANGE) } /> + { LocalizeText('wiredfurni.params.time.range') } + + - + +
+
+ ); +}; + +export const WiredConditionMatchTimeView: FC<{}> = () => +{ + const { trigger = null, setIntParams = null } = useWired(); + const [ hourMode, setHourMode ] = useState(MODE_SKIP); + const [ hourFrom, setHourFrom ] = useState(0); + const [ hourTo, setHourTo ] = useState(0); + const [ minuteMode, setMinuteMode ] = useState(MODE_SKIP); + const [ minuteFrom, setMinuteFrom ] = useState(0); + const [ minuteTo, setMinuteTo ] = useState(0); + const [ secondMode, setSecondMode ] = useState(MODE_SKIP); + const [ secondFrom, setSecondFrom ] = useState(0); + const [ secondTo, setSecondTo ] = useState(0); + + useEffect(() => + { + if(!trigger) return; + + setHourMode(MODE_OPTIONS.includes(trigger.intData[0]) ? trigger.intData[0] : MODE_SKIP); + setHourFrom(clampValue(trigger.intData[1] ?? 0, 0, 23)); + setHourTo(clampValue(trigger.intData[2] ?? 0, 0, 23)); + setMinuteMode(MODE_OPTIONS.includes(trigger.intData[3]) ? trigger.intData[3] : MODE_SKIP); + setMinuteFrom(clampValue(trigger.intData[4] ?? 0, 0, 59)); + setMinuteTo(clampValue(trigger.intData[5] ?? 0, 0, 59)); + setSecondMode(MODE_OPTIONS.includes(trigger.intData[6]) ? trigger.intData[6] : MODE_SKIP); + setSecondFrom(clampValue(trigger.intData[7] ?? 0, 0, 59)); + setSecondTo(clampValue(trigger.intData[8] ?? 0, 0, 59)); + }, [ trigger ]); + + const save = () => + { + setIntParams([ + hourMode, + clampValue(hourFrom, 0, 23), + clampValue(hourTo, 0, 23), + minuteMode, + clampValue(minuteFrom, 0, 59), + clampValue(minuteTo, 0, 59), + secondMode, + clampValue(secondFrom, 0, 59), + clampValue(secondTo, 0, 59) + ]); + }; + + return ( + +
+ setHourFrom(clampValue(value, 0, 23)) } + onModeChange={ setHourMode } + onToChange={ value => setHourTo(clampValue(value, 0, 23)) } /> + setMinuteFrom(clampValue(value, 0, 59)) } + onModeChange={ setMinuteMode } + onToChange={ value => setMinuteTo(clampValue(value, 0, 59)) } /> + setSecondFrom(clampValue(value, 0, 59)) } + onModeChange={ setSecondMode } + onToChange={ value => setSecondTo(clampValue(value, 0, 59)) } /> +
+
+ ); +}; diff --git a/src/components/wired/views/conditions/WiredConditionTeamHasRankView.tsx b/src/components/wired/views/conditions/WiredConditionTeamHasRankView.tsx new file mode 100644 index 0000000..1df5347 --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionTeamHasRankView.tsx @@ -0,0 +1,102 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredSourcesSelector } from '../WiredSourcesSelector'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +const TEAM_OPTIONS = [ 0, 1, 2, 3, 4 ]; +const PLACEMENT_OPTIONS = [ 1, 2, 3, 4 ]; + +export const WiredConditionTeamHasRankView: FC<{}> = () => +{ + const { trigger = null, setIntParams = null } = useWired(); + const [ team, setTeam ] = useState(1); + const [ placement, setPlacement ] = useState(1); + const [ userSource, setUserSource ] = useState(0); + const [ quantifier, setQuantifier ] = useState(0); + const [ showAdvanced, setShowAdvanced ] = useState(false); + + useEffect(() => + { + if(!trigger) return; + + const nextTeam = (trigger.intData.length > 0) ? trigger.intData[0] : 1; + const nextPlacement = (trigger.intData.length > 1) ? trigger.intData[1] : 1; + const nextUserSource = (trigger.intData.length > 2) ? trigger.intData[2] : 0; + const nextQuantifier = (trigger.intData.length > 3) ? trigger.intData[3] : 0; + + setTeam(TEAM_OPTIONS.includes(nextTeam) ? nextTeam : 1); + setPlacement(PLACEMENT_OPTIONS.includes(nextPlacement) ? nextPlacement : 1); + setUserSource(nextUserSource); + setQuantifier((nextQuantifier === 1) ? 1 : 0); + setShowAdvanced(nextUserSource !== 0 || nextQuantifier !== 0); + }, [ trigger ]); + + const save = () => + { + setIntParams([ + team, + placement, + userSource, + quantifier + ]); + }; + + return ( + + + { showAdvanced && + <> +
+ { LocalizeText('wiredfurni.params.quantifier_selection') } + { [ 0, 1 ].map(value => + { + return ( +
+ setQuantifier(value) } /> + { LocalizeText(`wiredfurni.params.quantifier.users.${ value }`) } +
+ ); + }) } +
+ + } + + }> +
+ { LocalizeText('wiredfurni.params.team') } + { TEAM_OPTIONS.map(value => + { + const labelKey = (value === 0) ? 'wiredfurni.params.team.triggerer' : `wiredfurni.params.team.${ value }`; + + return ( +
+ setTeam(value) } /> + { LocalizeText(labelKey) } +
+ ); + }) } +
+
+ { LocalizeText('wiredfurni.params.placement_selection') } + { PLACEMENT_OPTIONS.map(value => + { + return ( +
+ setPlacement(value) } /> + { LocalizeText(`wiredfurni.params.placement.${ value }`) } +
+ ); + }) } +
+
+ ); +}; diff --git a/src/components/wired/views/conditions/WiredConditionTeamHasScoreView.tsx b/src/components/wired/views/conditions/WiredConditionTeamHasScoreView.tsx new file mode 100644 index 0000000..a2317aa --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionTeamHasScoreView.tsx @@ -0,0 +1,158 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Slider, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredSourcesSelector } from '../WiredSourcesSelector'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +const TEAM_OPTIONS = [ 1, 2, 3, 4 ]; +const COMPARISON_OPTIONS = [ 0, 1, 2 ]; +const MIN_SCORE = 0; +const MAX_SCORE = 999; +const SCORE_PATTERN = /^\d*$/; + +const clampScore = (value: number) => +{ + if(isNaN(value)) return MIN_SCORE; + + return Math.max(MIN_SCORE, Math.min(MAX_SCORE, Math.floor(value))); +}; + +export const WiredConditionTeamHasScoreView: FC<{}> = () => +{ + const { trigger = null, setIntParams = null } = useWired(); + const [ team, setTeam ] = useState(1); + const [ comparison, setComparison ] = useState(1); + const [ score, setScore ] = useState(0); + const [ scoreInput, setScoreInput ] = useState('0'); + const [ userSource, setUserSource ] = useState(0); + const [ quantifier, setQuantifier ] = useState(0); + const [ showAdvanced, setShowAdvanced ] = useState(false); + + useEffect(() => + { + if(!trigger) return; + + const nextTeam = (trigger.intData.length > 0) ? trigger.intData[0] : 1; + const nextComparison = (trigger.intData.length > 1) ? trigger.intData[1] : 1; + const nextScore = clampScore((trigger.intData.length > 2) ? trigger.intData[2] : 0); + const nextUserSource = (trigger.intData.length > 3) ? trigger.intData[3] : 0; + const nextQuantifier = (trigger.intData.length > 4) ? trigger.intData[4] : 0; + + setTeam(TEAM_OPTIONS.includes(nextTeam) ? nextTeam : 1); + setComparison(COMPARISON_OPTIONS.includes(nextComparison) ? nextComparison : 1); + setScore(nextScore); + setScoreInput(nextScore.toString()); + setUserSource(nextUserSource); + setQuantifier((nextQuantifier === 1) ? 1 : 0); + setShowAdvanced(nextUserSource !== 0 || nextQuantifier !== 0); + }, [ trigger ]); + + const updateScore = (value: number) => + { + const nextValue = clampScore(value); + + setScore(nextValue); + setScoreInput(nextValue.toString()); + }; + + const updateScoreInput = (value: string) => + { + if(!SCORE_PATTERN.test(value)) return; + + setScoreInput(value); + + if(!value.length) + { + setScore(0); + return; + } + + updateScore(parseInt(value)); + }; + + const save = () => + { + setIntParams([ + team, + comparison, + clampScore(score), + userSource, + quantifier + ]); + }; + + return ( + + + { showAdvanced && + <> +
+ { LocalizeText('wiredfurni.params.quantifier_selection') } + { [ 0, 1 ].map(value => + { + return ( +
+ setQuantifier(value) } /> + { LocalizeText(`wiredfurni.params.quantifier.users.${ value }`) } +
+ ); + }) } +
+ + } + + }> +
+ { LocalizeText('wiredfurni.params.team') } + { TEAM_OPTIONS.map(value => + { + return ( +
+ setTeam(value) } /> + { LocalizeText(`wiredfurni.params.team.${ value }`) } +
+ ); + }) } +
+
+ { LocalizeText('wiredfurni.params.comparison_selection') } + { COMPARISON_OPTIONS.map(value => + { + return ( +
+ setComparison(value) } /> + { LocalizeText(`wiredfurni.params.comparison.${ value }`) } +
+ ); + }) } +
+
+ { LocalizeText('wiredfurni.params.setscore2') } + setScoreInput(clampScore(score).toString()) } + onChange={ event => updateScoreInput(event.target.value) } /> +
+
+ updateScore(event as number) } /> + { score } +
+
+ ); +}; diff --git a/src/components/wired/views/conditions/WiredConditionTriggererMatchView.tsx b/src/components/wired/views/conditions/WiredConditionTriggererMatchView.tsx new file mode 100644 index 0000000..d66403b --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionTriggererMatchView.tsx @@ -0,0 +1,130 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { NitroInput } from '../../../../layout'; +import { WiredSourceOption, WiredSourcesSelector } from '../WiredSourcesSelector'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +const ENTITY_HABBO = 1; +const ENTITY_PET = 2; +const ENTITY_BOT = 4; +const AVATAR_MODE_ANY = 0; +const AVATAR_MODE_CERTAIN = 1; +const SOURCE_SPECIFIED_USERNAME = 101; + +const MATCH_USER_SOURCES: WiredSourceOption[] = [ + { value: 0, label: 'wiredfurni.params.sources.users.0' }, + { value: 200, label: 'wiredfurni.params.sources.users.200' }, + { value: 201, label: 'wiredfurni.params.sources.users.201' } +]; + +const COMPARE_USER_SOURCES: WiredSourceOption[] = [ + ...MATCH_USER_SOURCES, + { value: SOURCE_SPECIFIED_USERNAME, label: 'wiredfurni.params.sources.users.101' } +]; + +export const WiredConditionTriggererMatchView: FC<{}> = () => +{ + const [ entityType, setEntityType ] = useState(ENTITY_HABBO); + const [ avatarMode, setAvatarMode ] = useState(AVATAR_MODE_ANY); + const [ username, setUsername ] = useState(''); + const [ matchUserSource, setMatchUserSource ] = useState(0); + const [ compareUserSource, setCompareUserSource ] = useState(0); + const [ quantifier, setQuantifier ] = useState(0); + const [ showAdvanced, setShowAdvanced ] = useState(false); + const { trigger = null, setIntParams = null, setStringParam = null } = useWired(); + + const needsUsername = (avatarMode === AVATAR_MODE_CERTAIN) || (compareUserSource === SOURCE_SPECIFIED_USERNAME); + + const save = () => + { + setIntParams([ + entityType, + avatarMode, + matchUserSource, + compareUserSource, + quantifier + ]); + setStringParam(username); + }; + + useEffect(() => + { + if(!trigger) return; + + setEntityType((trigger.intData.length > 0) ? trigger.intData[0] : ENTITY_HABBO); + setAvatarMode((trigger.intData.length > 1) ? trigger.intData[1] : AVATAR_MODE_ANY); + setMatchUserSource((trigger.intData.length > 2) ? trigger.intData[2] : 0); + setCompareUserSource((trigger.intData.length > 3) ? trigger.intData[3] : 0); + setQuantifier((trigger.intData.length > 4) ? trigger.intData[4] : 0); + setUsername(trigger.stringData || ''); + setShowAdvanced((trigger.intData.length > 2) ? (trigger.intData[2] !== 0 || trigger.intData[3] !== 0 || trigger.intData[4] !== 0) : false); + }, [ trigger ]); + + return ( + + + { showAdvanced && + <> +
+ { LocalizeText('wiredfurni.params.quantifier_selection') } + { [ 0, 1 ].map(value => + { + return ( +
+ setQuantifier(value) } /> + { LocalizeText(`wiredfurni.params.quantifier.users.${ value }`) } +
+ ); + }) } +
+ + + } + + }> +
+ { [ ENTITY_HABBO, ENTITY_PET, ENTITY_BOT ].map(value => + { + return ( +
+ setEntityType(value) } /> + { LocalizeText(`wiredfurni.params.usertype.${ value }`) } +
+ ); + }) } +
+
+ { LocalizeText('wiredfurni.params.picktriggerer') } +
+ setAvatarMode(AVATAR_MODE_ANY) } /> + { LocalizeText('wiredfurni.params.anyavatar') } +
+
+ setAvatarMode(AVATAR_MODE_CERTAIN) } /> + { LocalizeText('wiredfurni.params.certainavatar') } +
+ { needsUsername && + setUsername(event.target.value) } /> } +
+
+ ); +}; diff --git a/src/components/wired/views/conditions/WiredConditionUserPerformsActionView.tsx b/src/components/wired/views/conditions/WiredConditionUserPerformsActionView.tsx new file mode 100644 index 0000000..a26c859 --- /dev/null +++ b/src/components/wired/views/conditions/WiredConditionUserPerformsActionView.tsx @@ -0,0 +1,151 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredSourceOption, WiredSourcesSelector } from '../WiredSourcesSelector'; +import { WiredConditionBaseView } from './WiredConditionBaseView'; + +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' } +]; + +const USER_ACTION_SOURCES: WiredSourceOption[] = [ + { value: 0, label: 'wiredfurni.params.sources.users.0' }, + { value: 200, label: 'wiredfurni.params.sources.users.200' }, + { value: 201, label: 'wiredfurni.params.sources.users.201' } +]; + +interface WiredConditionUserPerformsActionViewProps +{ + negative?: boolean; +} + +export const WiredConditionUserPerformsActionView: FC = props => +{ + const { negative = false } = props; + 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 [ userSource, setUserSource ] = useState(0); + const [ quantifier, setQuantifier ] = useState(0); + const [ showAdvanced, setShowAdvanced ] = useState(false); + const { trigger = null, setIntParams = null } = useWired(); + const quantifierKeyPrefix = negative ? 'wiredfurni.params.quantifier.users.neg' : 'wiredfurni.params.quantifier.users'; + + const save = () => setIntParams([ + selectedAction, + signFilterEnabled ? 1 : 0, + signId, + danceFilterEnabled ? 1 : 0, + danceId, + userSource, + quantifier + ]); + + 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); + setUserSource((trigger?.intData?.length > 5) ? trigger.intData[5] : 0); + setQuantifier((trigger?.intData?.length > 6) ? trigger.intData[6] : 0); + setShowAdvanced((trigger?.intData?.length > 5) ? (trigger.intData[5] !== 0 || trigger.intData[6] !== 0) : false); + }, [ trigger ]); + + return ( + + + { showAdvanced && + <> +
+ { LocalizeText('wiredfurni.params.quantifier_selection') } + { [ 0, 1 ].map(value => + { + return ( +
+ setQuantifier(value) } /> + { LocalizeText(`${ quantifierKeyPrefix }.${ value }`) } +
+ ); + }) } +
+ + } + + }> +
+ Action + +
+ { (selectedAction === ACTION_SIGN) && +
+
+ setSignFilterEnabled(event.target.checked) } /> + { LocalizeText('wiredfurni.params.sign_filter') } +
+ { signFilterEnabled && + } +
} + { (selectedAction === ACTION_DANCE) && +
+
+ setDanceFilterEnabled(event.target.checked) } /> + { LocalizeText('wiredfurni.params.dance_filter') } +
+ { danceFilterEnabled && + } +
} +
+ ); +};