diff --git a/package.json b/package.json index 716c4bf..4dab2f5 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,6 @@ "framer-motion": "^11.2.12", "react": "^19.2.4", "react-bootstrap": "^2.10.10", - "react-colorful": "^5.6.1", "react-dom": "^19.2.4", "react-icons": "^5.5.0", "react-slider": "^2.0.6", 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/public/ui-config.json b/public/ui-config.json deleted file mode 100644 index cec08d1..0000000 --- a/public/ui-config.json +++ /dev/null @@ -1,2434 +0,0 @@ -{ - "external.plugins": [ - "plugins/room-builder.js" - ], - "ui.header.images.count": 30, - "ui.header.images.url": "https://image.webbo.city/image/headerImage/image{id}.gif", - "image.library.notifications.url": "${image.library.url}notifications/%image%.png", - "achievements.images.url": "${image.library.url}Quests/%image%.png", - "camera.url": "/swf/usercontent/camera/", - "thumbnails.url": "/swf/usercontent/thumbnails/%thumbnail%.png", - "url.prefix": "", - "habbopages.url": "/swf/habbopages/", - "group.homepage.url": "${url.prefix}/groups/%groupid%/id", - "guide.help.alpha.groupid": 0, - "chat.viewer.height.percentage": 0.4, - "pathfinder.underpass.height": 1.5, - "widget.dimmer.colorwheel": false, - "avatar.wardrobe.max.slots": 10, - "user.badges.max.slots": 6, - "user.badges.group.slot.enabled": false, - "user.tags.enabled": false, - "camera.publish.disabled": false, - "hc.disabled": false, - "badge.descriptions.enabled": true, - "motto.max.length": 38, - "bot.name.max.length": 15, - "pet.package.name.max.length": 15, - "wired.action.bot.talk.to.avatar.max.length": 64, - "wired.action.bot.talk.max.length": 64, - "wired.action.chat.max.length": 100, - "wired.action.kick.from.room.max.length": 100, - "wired.action.mute.user.max.length": 100, - "game.center.enabled": false, - "guides.enabled": true, - "toolbar.hide.quests": true, - "navigator.room.models": [{ - "clubLevel": 0, - "tileSize": 104, - "name": "a" - }, { - "clubLevel": 0, - "tileSize": 94, - "name": "b" - }, { - "clubLevel": 0, - "tileSize": 36, - "name": "c" - }, { - "clubLevel": 0, - "tileSize": 84, - "name": "d" - }, { - "clubLevel": 0, - "tileSize": 80, - "name": "e" - }, { - "clubLevel": 0, - "tileSize": 80, - "name": "f" - }, { - "clubLevel": 0, - "tileSize": 416, - "name": "i" - }, { - "clubLevel": 0, - "tileSize": 320, - "name": "j" - }, { - "clubLevel": 0, - "tileSize": 448, - "name": "k" - }, { - "clubLevel": 0, - "tileSize": 352, - "name": "l" - }, { - "clubLevel": 0, - "tileSize": 384, - "name": "m" - }, { - "clubLevel": 0, - "tileSize": 372, - "name": "n" - }, { - "clubLevel": 1, - "tileSize": 80, - "name": "g" - }, { - "clubLevel": 1, - "tileSize": 74, - "name": "h" - }, { - "clubLevel": 1, - "tileSize": 416, - "name": "o" - }, { - "clubLevel": 1, - "tileSize": 352, - "name": "p" - }, { - "clubLevel": 1, - "tileSize": 304, - "name": "q" - }, { - "clubLevel": 1, - "tileSize": 336, - "name": "r" - }, { - "clubLevel": 1, - "tileSize": 748, - "name": "u" - }, { - "clubLevel": 1, - "tileSize": 438, - "name": "v" - }, { - "clubLevel": 2, - "tileSize": 540, - "name": "t" - }, { - "clubLevel": 2, - "tileSize": 512, - "name": "w" - }, { - "clubLevel": 2, - "tileSize": 396, - "name": "x" - }, { - "clubLevel": 2, - "tileSize": 440, - "name": "y" - }, { - "clubLevel": 2, - "tileSize": 456, - "name": "z" - }, { - "clubLevel": 2, - "tileSize": 208, - "name": "0" - }, { - "clubLevel": 2, - "tileSize": 1009, - "name": "1" - }, { - "clubLevel": 2, - "tileSize": 1044, - "name": "2" - }, { - "clubLevel": 2, - "tileSize": 183, - "name": "3" - }, { - "clubLevel": 2, - "tileSize": 254, - "name": "4" - }, { - "clubLevel": 2, - "tileSize": 1024, - "name": "5" - }, { - "clubLevel": 2, - "tileSize": 801, - "name": "6" - }, { - "clubLevel": 2, - "tileSize": 354, - "name": "7" - }, { - "clubLevel": 2, - "tileSize": 888, - "name": "8" - }, { - "clubLevel": 2, - "tileSize": 926, - "name": "9" - } - ], - "backgrounds.data": [{ - "backgroundId": 0, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 1, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 2, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 3, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 4, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 5, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 6, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 7, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 8, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 9, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 10, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 11, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 12, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 13, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 14, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 15, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 16, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 17, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 18, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 19, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 20, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 21, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 22, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 23, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 24, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 25, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 26, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 27, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 28, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 29, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 30, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 31, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 32, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 33, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 34, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 35, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 36, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 37, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 38, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 39, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 40, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 41, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 42, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 43, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 44, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 45, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 46, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 47, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 48, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 49, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 50, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 51, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 52, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 53, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 54, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 55, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 56, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 57, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 58, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 59, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 60, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 61, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 62, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 63, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 64, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 65, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 66, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 67, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 68, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 69, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 70, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 71, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 72, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 73, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 74, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 75, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 76, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 77, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 78, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 79, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 80, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 81, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 82, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 83, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 84, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 85, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 86, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 87, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 88, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 89, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 90, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 91, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 92, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 93, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 94, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 95, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 96, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 97, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 98, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 99, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 100, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 101, - "minRank": 2, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "backgroundId": 102, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 103, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 104, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 105, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 106, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 107, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 108, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 109, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 110, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 111, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 112, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 113, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 114, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 115, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 116, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 117, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 118, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 119, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 120, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 121, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 122, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 123, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 124, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 125, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 126, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 127, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 128, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 129, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 130, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 131, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 132, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 133, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 134, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 135, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 136, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 137, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 138, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 139, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 140, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 141, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 142, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 143, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 144, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 145, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 146, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 147, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 148, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 149, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 150, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 151, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 152, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 153, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 154, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 155, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 156, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 157, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 158, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 159, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 160, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 161, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 162, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 163, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 164, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 165, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 166, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 167, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 168, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 169, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 170, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 171, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 172, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 173, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 174, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 175, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 176, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 177, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 178, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 179, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 180, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 181, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 182, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 183, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 184, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 185, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 186, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "backgroundId": 187, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - } - ], - "stands.data": [{ - "standId": 0, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "standId": 1, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "standId": 2, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "standId": 3, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "standId": 4, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "standId": 5, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "standId": 6, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "standId": 7, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "standId": 8, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "standId": 9, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "standId": 10, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "standId": 11, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "standId": 12, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "standId": 13, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "standId": 14, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "standId": 15, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "standId": 16, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "standId": 17, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "standId": 18, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "standId": 19, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "standId": 20, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "standId": 21, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - } - ], - "overlays.data": [{ - "overlayId": 0, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "overlayId": 1, - "minRank": 0, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "overlayId": 2, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "overlayId": 3, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "overlayId": 4, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "overlayId": 5, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "overlayId": 6, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "overlayId": 7, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "overlayId": 8, - "minRank": 0, - "isHcOnly": true, - "isAmbassadorOnly": false - } - ], - "hotelview": { - "room.pool": "791", - "room.picnic": "2193", - "room.rooftop": "", - "room.rooftop.pool": "", - "room.peaceful": "", - "room.infobus": "5956", - "room.lobby": "1450", - "show.avatar": true, - "widgets": { - "slot.1.widget": "", - "slot.1.conf": {}, - "slot.2.widget": "", - "slot.2.conf": { - "image": "", - "texts": "", - "btnLink": "" - }, - "slot.3.widget": "", - "slot.3.conf": {}, - "slot.4.widget": "", - "slot.4.conf": {}, - "slot.5.widget": "", - "slot.5.conf": {}, - "slot.6.widget": "", - "slot.6.conf": { - "campaign": "" - }, - "slot.7.widget": "", - "slot.7.conf": {} - }, - "images": { - "background": "${asset.url}/images/reception/stretch_blue.png", - "background.colour": "#8ee0f0", - "sun": "${asset.url}/images/reception/sun.png", - "drape": "${asset.url}/images/reception/drape.png", - "left": "", - "right": "", - "right.repeat": "" - } - }, - "achievements.unseen.ignored": [ - "ACH_AllTimeHotelPresence" - ], - "avatareditor.show.clubitems.dimmed": true, - "avatareditor.show.clubitems.first": true, - "chat.history.max.items": 100, - "system.currency.types": [ - -1, - 0, - 5 - ], - "catalog.links": { - "hc.buy_hc": "habbo_club", - "hc.hc_gifts": "club_gifts", - "pets.buy_food": "pet_food", - "pets.buy_saddle": "saddles" - }, - "hc.center": { - "benefits.info": true, - "payday.info": true, - "gift.info": true, - "benefits.habbopage": "habboclub", - "payday.habbopage": "hcpayday" - }, - "respect.options": { - "enabled": false, - "sound": "sound_respect_received" - }, - "currency.display.number.short": false, - "currency.asset.icon.url": "${images.url}/wallet/%type%.png", - "catalog.asset.url": "${image.library.url}catalogue", - "catalog.asset.image.url": "${catalog.asset.url}/%name%.gif", - "catalog.asset.icon.url": "${catalog.asset.url}/icon_%name%.png", - "catalog.tab.icons": false, - "catalog.headers": false, - "chat.input.maxlength": 100, - "chat.styles.disabled": [], - "chat.styles": [{ - "styleId": 0, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "styleId": 1, - "minRank": 5, - "isSystemStyle": true, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "styleId": 2, - "minRank": 5, - "isSystemStyle": true, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "styleId": 3, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "styleId": 4, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "styleId": 5, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "styleId": 6, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "styleId": 7, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "styleId": 8, - "minRank": 5, - "isSystemStyle": true, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "styleId": 9, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "styleId": 10, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "styleId": 11, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "styleId": 12, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "styleId": 13, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "styleId": 14, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "styleId": 15, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "styleId": 16, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "styleId": 17, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "styleId": 18, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "styleId": 19, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "styleId": 20, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "styleId": 21, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "styleId": 22, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "styleId": 23, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "styleId": 24, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "styleId": 25, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "styleId": 26, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "styleId": 27, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "styleId": 28, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "styleId": 29, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "styleId": 30, - "minRank": 5, - "isSystemStyle": true, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "styleId": 31, - "minRank": 5, - "isSystemStyle": true, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "styleId": 32, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "styleId": 33, - "minRank": 5, - "isSystemStyle": true, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "styleId": 34, - "minRank": 5, - "isSystemStyle": true, - "isHcOnly": false, - "isAmbassadorOnly": false - }, { - "styleId": 35, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "styleId": 36, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "styleId": 37, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": true - }, { - "styleId": 38, - "minRank": 0, - "isSystemStyle": false, - "isHcOnly": true, - "isAmbassadorOnly": false - }, { - "styleId": 39, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": true - }, { - "styleId": 40, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": true - }, { - "styleId": 41, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": true - }, { - "styleId": 42, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": true - }, { - "styleId": 43, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": true - }, { - "styleId": 44, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": true - }, { - "styleId": 45, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": true - }, { - "styleId": 46, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": true - }, { - "styleId": 47, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": true - }, { - "styleId": 48, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": true - }, { - "styleId": 49, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": true - }, { - "styleId": 50, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": true - }, { - "styleId": 51, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": true - }, { - "styleId": 52, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": true - }, { - "styleId": 53, - "minRank": 5, - "isSystemStyle": false, - "isHcOnly": false, - "isAmbassadorOnly": true - } - ], - "camera.available.effects": [{ - "name": "dark_sepia", - "colorMatrix": [ - 0.4, - 0.4, - 0.1, - 0, - 110, - 0.3, - 0.4, - 0.1, - 0, - 30, - 0.3, - 0.2, - 0.1, - 0, - 0, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 0, - "enabled": true - }, { - "name": "increase_saturation", - "colorMatrix": [ - 2, - -0.5, - -0.5, - 0, - 0, - -0.5, - 2, - -0.5, - 0, - 0, - -0.5, - -0.5, - 2, - 0, - 0, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 0, - "enabled": true - }, { - "name": "increase_contrast", - "colorMatrix": [ - 1.5, - 0, - 0, - 0, - -50, - 0, - 1.5, - 0, - 0, - -50, - 0, - 0, - 1.5, - 0, - -50, - 0, - 0, - 0, - 1.5, - 0 - ], - "minLevel": 0, - "enabled": true - }, { - "name": "shadow_multiply_02", - "colorMatrix": [], - "minLevel": 0, - "blendMode": 2, - "enabled": true - }, { - "name": "color_1", - "colorMatrix": [ - 0.393, - 0.769, - 0.189, - 0, - 0, - 0.349, - 0.686, - 0.168, - 0, - 0, - 0.272, - 0.534, - 0.131, - 0, - 0, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 1, - "enabled": true - }, { - "name": "hue_bright_sat", - "colorMatrix": [ - 1, - 0.6, - 0.2, - 0, - -50, - 0.2, - 1, - 0.6, - 0, - -50, - 0.6, - 0.2, - 1, - 0, - -50, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 1, - "enabled": true - }, { - "name": "hearts_hardlight_02", - "colorMatrix": [], - "minLevel": 1, - "blendMode": 9, - "enabled": true - }, { - "name": "texture_overlay", - "colorMatrix": [], - "minLevel": 1, - "blendMode": 4, - "enabled": true - }, { - "name": "pinky_nrm", - "colorMatrix": [], - "minLevel": 1, - "blendMode": 0, - "enabled": true - }, { - "name": "color_2", - "colorMatrix": [ - 0.333, - 0.333, - 0.333, - 0, - 0, - 0.333, - 0.333, - 0.333, - 0, - 0, - 0.333, - 0.333, - 0.333, - 0, - 0, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 2, - "enabled": true - }, { - "name": "night_vision", - "colorMatrix": [ - 0, - 0, - 0, - 0, - 0, - 0, - 1.1, - 0, - 0, - -50, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 2, - "enabled": true - }, { - "name": "stars_hardlight_02", - "colorMatrix": [], - "minLevel": 2, - "blendMode": 9, - "enabled": true - }, { - "name": "coffee_mpl", - "colorMatrix": [], - "minLevel": 2, - "blendMode": 2, - "enabled": true - }, { - "name": "security_hardlight", - "colorMatrix": [], - "minLevel": 3, - "blendMode": 9, - "enabled": true - }, { - "name": "bluemood_mpl", - "colorMatrix": [], - "minLevel": 3, - "blendMode": 2, - "enabled": true - }, { - "name": "rusty_mpl", - "colorMatrix": [], - "minLevel": 3, - "blendMode": 2, - "enabled": true - }, { - "name": "decr_conrast", - "colorMatrix": [ - 0.5, - 0, - 0, - 0, - 50, - 0, - 0.5, - 0, - 0, - 50, - 0, - 0, - 0.5, - 0, - 50, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 4, - "enabled": true - }, { - "name": "green_2", - "colorMatrix": [ - 0.5, - 0.5, - 0.5, - 0, - 0, - 0.5, - 0.5, - 0.5, - 0, - 90, - 0.5, - 0.5, - 0.5, - 0, - 0, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 4, - "enabled": true - }, { - "name": "alien_hrd", - "colorMatrix": [], - "minLevel": 4, - "blendMode": 9, - "enabled": true - }, { - "name": "color_3", - "colorMatrix": [ - 0.609, - 0.609, - 0.082, - 0, - 0, - 0.309, - 0.609, - 0.082, - 0, - 0, - 0.309, - 0.609, - 0.082, - 0, - 0, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 5, - "enabled": true - }, { - "name": "color_4", - "colorMatrix": [ - 0.8, - -0.8, - 1, - 0, - 70, - 0.8, - -0.8, - 1, - 0, - 70, - 0.8, - -0.8, - 1, - 0, - 70, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 5, - "enabled": true - }, { - "name": "toxic_hrd", - "colorMatrix": [], - "minLevel": 5, - "blendMode": 9, - "enabled": true - }, { - "name": "hypersaturated", - "colorMatrix": [ - 2, - -1, - 0, - 0, - 0, - -1, - 2, - 0, - 0, - 0, - 0, - -1, - 2, - 0, - 0, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 6, - "enabled": true - }, { - "name": "Yellow", - "colorMatrix": [ - 1, - 0, - 0, - 0, - 0, - 0, - 1, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 6, - "enabled": true - }, { - "name": "misty_hrd", - "colorMatrix": [], - "minLevel": 6, - "blendMode": 9, - "enabled": true - }, { - "name": "x_ray", - "colorMatrix": [ - 0, - 1.2, - 0, - 0, - -100, - 0, - 2, - 0, - 0, - -120, - 0, - 2, - 0, - 0, - -120, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 7, - "enabled": true - }, { - "name": "decrease_saturation", - "colorMatrix": [ - 0.7, - 0.2, - 0.2, - 0, - 0, - 0.2, - 0.7, - 0.2, - 0, - 0, - 0.2, - 0.2, - 0.7, - 0, - 0, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 7, - "enabled": true - }, { - "name": "drops_mpl", - "colorMatrix": [], - "minLevel": 8, - "blendMode": 2, - "enabled": true - }, { - "name": "shiny_hrd", - "colorMatrix": [], - "minLevel": 9, - "blendMode": 9, - "enabled": true - }, { - "name": "glitter_hrd", - "colorMatrix": [], - "minLevel": 10, - "blendMode": 9, - "enabled": true - }, { - "name": "frame_gold", - "colorMatrix": [], - "minLevel": 10, - "blendMode": 0, - "enabled": true - }, { - "name": "frame_gray_4", - "colorMatrix": [], - "minLevel": 10, - "blendMode": 0, - "enabled": true - }, { - "name": "frame_black_2", - "colorMatrix": [], - "minLevel": 10, - "blendMode": 0, - "enabled": true - }, { - "name": "frame_wood_2", - "colorMatrix": [], - "minLevel": 10, - "blendMode": 0, - "enabled": true - }, { - "name": "finger_nrm", - "colorMatrix": [], - "minLevel": 10, - "blendMode": 0, - "enabled": true - }, { - "name": "color_5", - "colorMatrix": [ - 3.309, - 0.609, - 1.082, - 0.2, - 0, - 0.309, - 0.609, - 0.082, - 0, - 0, - 1.309, - 0.609, - 0.082, - 0, - 0, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 10, - "enabled": true - }, { - "name": "black_white_negative", - "colorMatrix": [ - -0.5, - -0.5, - -0.5, - 0, - 0, - -0.5, - -0.5, - -0.5, - 0, - 0, - -0.5, - -0.5, - -0.5, - 0, - 0, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 10, - "enabled": true - }, { - "name": "blue", - "colorMatrix": [ - 0.5, - 0.5, - 0.5, - 0, - -255, - 0.5, - 0.5, - 0.5, - 0, - -170, - 0.5, - 0.5, - 0.5, - 0, - 0, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 10, - "enabled": true - }, { - "name": "red", - "colorMatrix": [ - 0.5, - 0.5, - 0.5, - 0, - 0, - 0.5, - 0.5, - 0.5, - 0, - -170, - 0.5, - 0.5, - 0.5, - 0, - -170, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 10, - "enabled": true - }, { - "name": "green", - "colorMatrix": [ - 0.5, - 0.5, - 0.5, - 0, - -170, - 0.5, - 0.5, - 0.5, - 0, - 0, - 0.5, - 0.5, - 0.5, - 0, - -170, - 0, - 0, - 0, - 1, - 0 - ], - "minLevel": 10, - "enabled": true - } - ], - "notification": { - "notification.admin.transient": { - "display": "POP_UP", - "image": "${image.library.url}/album1358/frank_wave_001.gif" - }, - "notification.builders_club.membership_expired": { - "display": "POP_UP" - }, - "notification.builders_club.membership_expires": { - "display": "POP_UP", - "image": "${image.library.url}/notifications/builders_club_room_locked_small.png" - }, - "notification.builders_club.membership_extended": { - "delivery": "PERSISTENT", - "display": "POP_UP" - }, - "notification.builders_club.membership_made": { - "delivery": "PERSISTENT", - "display": "POP_UP", - "image": "${image.library.url}/notifications/builders_club_membership_extended.png" - }, - "notification.builders_club.membership_renewed": { - "delivery": "PERSISTENT", - "display": "POP_UP", - "image": "${image.library.url}/notifications/builders_club_membership_extended.png" - }, - "notification.builders_club.room_locked": { - "display": "BUBBLE", - "image": "${image.library.url}/notifications/builders_club_room_locked_small.png" - }, - "notification.builders_club.room_unlocked": { - "display": "BUBBLE" - }, - "notification.builders_club.visit_denied_for_owner": { - "display": "BUBBLE", - "image": "${image.library.url}/notifications/builders_club_room_locked_small.png" - }, - "notification.builders_club.visit_denied_for_visitor": { - "display": "POP_UP", - "image": "${image.library.url}/notifications/builders_club_room_locked.png" - }, - "notification.campaign.credit.donation": { - "display": "BUBBLE" - }, - "notification.campaign.product.donation": { - "display": "BUBBLE" - }, - "notification.casino.too_many_dice.placement": { - "display": "POP_UP" - }, - "notification.casino.too_many_dice": { - "display": "POP_UP" - }, - "notification.cfh.created": { - "display": "POP_UP", - "title": "" - }, - "notification.feed.enabled": false, - "notification.floorplan_editor.error": { - "display": "POP_UP" - }, - "notification.forums.delivered": { - "delivery": "PERSISTENT", - "display": "POP_UP" - }, - "notification.forums.forum_settings_updated": { - "display": "BUBBLE" - }, - "notification.forums.message.hidden": { - "display": "BUBBLE" - }, - "notification.forums.message.restored": { - "display": "BUBBLE" - }, - "notification.forums.thread.hidden": { - "display": "BUBBLE" - }, - "notification.forums.thread.locked": { - "display": "BUBBLE" - }, - "notification.forums.thread.pinned": { - "display": "BUBBLE" - }, - "notification.forums.thread.restored": { - "display": "BUBBLE" - }, - "notification.forums.thread.unlocked": { - "display": "BUBBLE" - }, - "notification.forums.thread.unpinned": { - "display": "BUBBLE" - }, - "notification.furni_placement_error": { - "display": "BUBBLE" - }, - "notification.gifting.valentine": { - "delivery": "PERSISTENT", - "display": "BUBBLE", - "image": "${image.library.url}/notifications/polaroid_photo.png" - }, - "notification.items.enabled": true, - "notification.mute.forbidden.time": { - "display": "BUBBLE" - }, - "notification.npc.gift.received": { - "display": "BUBBLE", - "image": "${image.library.url}/album1584/X1517.gif" - } - } -} diff --git a/src/App.tsx b/src/App.tsx index f67bd6b..c97d6fa 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,9 +1,10 @@ import { GetAssetManager, GetAvatarRenderManager, GetCommunication, GetConfiguration, GetLocalizationManager, GetRoomEngine, GetRoomSessionManager, GetSessionDataManager, GetSoundManager, GetStage, GetTexturePool, GetTicker, HabboWebTools, LegacyExternalInterface, LoadGameUrlEvent, NitroLogger, NitroVersion, PrepareRenderer } from '@nitrots/nitro-renderer'; import { FC, useEffect, useState } from 'react'; -import { GetUIVersion, UiSettingsProvider } from './api'; +import { GetUIVersion } from './api'; import { Base } from './common'; import { LoadingView } from './components/loading/LoadingView'; import { MainView } from './components/MainView'; +import { ReconnectView } from './components/reconnect/ReconnectView'; import { useMessageEvent } from './hooks'; NitroVersion.UI_VERSION = GetUIVersion(); @@ -89,13 +90,12 @@ export const App: FC<{}> = props => }, []); return ( - - - { !isReady && - } - { isReady && } - - - + + { !isReady && + } + { isReady && } + + + ); }; \ No newline at end of file diff --git a/src/api/ui-settings/IUiSettings.ts b/src/api/ui-settings/IUiSettings.ts deleted file mode 100644 index 24604b6..0000000 --- a/src/api/ui-settings/IUiSettings.ts +++ /dev/null @@ -1,21 +0,0 @@ -export interface IUiSettings -{ - colorMode: 'color' | 'image' | 'default'; - headerColor: string; - headerImageUrl: string; - headerAlpha: number; -} - -export const DEFAULT_UI_SETTINGS: IUiSettings = { - colorMode: 'default', - headerColor: '#1E7295', - headerImageUrl: '', - headerAlpha: 100 -}; - -export const PRESET_COLORS: string[] = [ - '#000000', '#444444', '#888888', '#CCCCCC', '#660000', '#CC3333', '#FF6666', '#CC6600', - '#FF3333', '#FF6633', '#FF9933', '#FFCC00', '#FFFF00', '#66FF00', '#00CC00', '#009900', - '#00FFCC', '#33CCFF', '#3366FF', '#0000CC', '#6633CC', '#9933FF', '#CC33FF', '#FF66CC', - '#FF99CC', '#1E7295', '#185D79', '#2DABC2', '#2B91A7', '#283F5D' -]; diff --git a/src/api/ui-settings/UiSettingsContext.tsx b/src/api/ui-settings/UiSettingsContext.tsx deleted file mode 100644 index 5d516cd..0000000 --- a/src/api/ui-settings/UiSettingsContext.tsx +++ /dev/null @@ -1,164 +0,0 @@ -import { createContext, FC, PropsWithChildren, useCallback, useContext, useEffect, useState } from 'react'; -import { DEFAULT_UI_SETTINGS, IUiSettings } from './IUiSettings'; - -const STORAGE_KEY = 'nitro.ui.settings'; - -interface IUiSettingsContext -{ - settings: IUiSettings; - isCustomActive: boolean; - updateSettings: (partial: Partial) => void; - resetSettings: () => void; - getHeaderStyle: () => React.CSSProperties; - getTabsStyle: () => React.CSSProperties; - getAccentColor: () => string; -} - -const UiSettingsContext = createContext({ - settings: DEFAULT_UI_SETTINGS, - isCustomActive: false, - updateSettings: () => {}, - resetSettings: () => {}, - getHeaderStyle: () => ({}), - getTabsStyle: () => ({}), - getAccentColor: () => DEFAULT_UI_SETTINGS.headerColor -}); - -const darkenColor = (hex: string, amount: number): string => -{ - const num = parseInt(hex.replace('#', ''), 16); - const r = Math.max(0, ((num >> 16) & 0xFF) - amount); - const g = Math.max(0, ((num >> 8) & 0xFF) - amount); - const b = Math.max(0, (num & 0xFF) - amount); - - return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); -}; - -const loadSettings = (): IUiSettings => -{ - try - { - const stored = localStorage.getItem(STORAGE_KEY); - if(stored) return { ...DEFAULT_UI_SETTINGS, ...JSON.parse(stored) }; - } - catch(e) {} - - return { ...DEFAULT_UI_SETTINGS }; -}; - -const saveSettings = (settings: IUiSettings): void => -{ - try - { - localStorage.setItem(STORAGE_KEY, JSON.stringify(settings)); - } - catch(e) {} -}; - -export const UiSettingsProvider: FC = ({ children }) => -{ - const [ settings, setSettings ] = useState(loadSettings); - - const updateSettings = useCallback((partial: Partial) => - { - setSettings(prev => - { - const updated = { ...prev, ...partial }; - saveSettings(updated); - return updated; - }); - }, []); - - const resetSettings = useCallback(() => - { - setSettings({ ...DEFAULT_UI_SETTINGS }); - saveSettings(DEFAULT_UI_SETTINGS); - }, []); - - const getHeaderStyle = useCallback((): React.CSSProperties => - { - if(settings.colorMode === 'color') - { - return { backgroundColor: settings.headerColor }; - } - - if(settings.colorMode === 'image' && settings.headerImageUrl) - { - return { - backgroundImage: `url(${ settings.headerImageUrl })`, - backgroundSize: 'cover', - backgroundPosition: 'center', - backgroundRepeat: 'repeat' - }; - } - - return {}; - }, [ settings ]); - - const getTabsStyle = useCallback((): React.CSSProperties => - { - if(settings.colorMode === 'color') - { - return { backgroundColor: darkenColor(settings.headerColor, 30) }; - } - - if(settings.colorMode === 'image' && settings.headerImageUrl) - { - return { - backgroundImage: `url(${ settings.headerImageUrl })`, - backgroundSize: 'cover', - backgroundPosition: 'center bottom', - backgroundRepeat: 'repeat' - }; - } - - return {}; - }, [ settings ]); - - const getAccentColor = useCallback((): string => - { - if(settings.colorMode === 'color') return settings.headerColor; - return DEFAULT_UI_SETTINGS.headerColor; - }, [ settings ]); - - const isCustomActive = settings.colorMode !== 'default'; - - const ALL_CSS_VARS = [ - '--ui-accent-color', '--ui-accent-dark', - '--ui-ctx-bg', '--ui-ctx-header-bg', '--ui-ctx-item-bg1', '--ui-ctx-item-bg2', - '--ui-btn-primary-bg', '--ui-btn-primary-border', - '--ui-dark-bg', '--ui-dark-border' - ]; - - useEffect(() => - { - const root = document.documentElement; - - if(settings.colorMode === 'color') - { - const c = settings.headerColor; - root.style.setProperty('--ui-accent-color', c); - root.style.setProperty('--ui-accent-dark', darkenColor(c, 30)); - root.style.setProperty('--ui-ctx-bg', darkenColor(c, 50)); - root.style.setProperty('--ui-ctx-header-bg', darkenColor(c, 20)); - root.style.setProperty('--ui-ctx-item-bg1', darkenColor(c, 60)); - root.style.setProperty('--ui-ctx-item-bg2', darkenColor(c, 70)); - root.style.setProperty('--ui-btn-primary-bg', c); - root.style.setProperty('--ui-btn-primary-border', darkenColor(c, 20)); - root.style.setProperty('--ui-dark-bg', darkenColor(c, 55)); - root.style.setProperty('--ui-dark-border', darkenColor(c, 60)); - } - else - { - ALL_CSS_VARS.forEach(v => root.style.removeProperty(v)); - } - }, [ settings ]); - - return ( - - { children } - - ); -}; - -export const useUiSettings = () => useContext(UiSettingsContext); diff --git a/src/api/ui-settings/index.ts b/src/api/ui-settings/index.ts deleted file mode 100644 index 255a5da..0000000 --- a/src/api/ui-settings/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './IUiSettings'; -export * from './UiSettingsContext'; diff --git a/src/api/wired/WiredActionLayoutCode.ts b/src/api/wired/WiredActionLayoutCode.ts index 3240a00..c9d4324 100644 --- a/src/api/wired/WiredActionLayoutCode.ts +++ b/src/api/wired/WiredActionLayoutCode.ts @@ -32,4 +32,9 @@ export class WiredActionLayoutCode public static USERS_AREA_SELECTOR: number = 31; public static USERS_NEIGHBORHOOD_SELECTOR: number = 32; public static SEND_SIGNAL: number = 33; + public static FREEZE: number = 34; + public static UNFREEZE: number = 35; + public static FURNI_TO_USER: number = 36; + public static USER_TO_FURNI: number = 37; + public static FURNI_TO_FURNI: number = 38; } diff --git a/src/api/wired/WiredConditionLayoutCode.ts b/src/api/wired/WiredConditionLayoutCode.ts index 58cae5d..b344f8b 100644 --- a/src/api/wired/WiredConditionLayoutCode.ts +++ b/src/api/wired/WiredConditionLayoutCode.ts @@ -26,4 +26,15 @@ export class WiredConditionlayout public static NOT_ACTOR_WEARING_EFFECT: number = 23; 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/api/wired/WiredSelectionVisualizer.ts b/src/api/wired/WiredSelectionVisualizer.ts index 18edbf7..06aeb57 100644 --- a/src/api/wired/WiredSelectionVisualizer.ts +++ b/src/api/wired/WiredSelectionVisualizer.ts @@ -3,25 +3,42 @@ import { GetRoomEngine, IRoomObject, IRoomObjectSpriteVisualization, RoomObjectC export class WiredSelectionVisualizer { private static _selectionShader: WiredFilter = new WiredFilter({ - lineColor: [ 1, 1, 1 ], - color: [ 0.6, 0.6, 0.6 ] + lineColor: [ 0.45, 0.95, 0.55 ], + color: [ 0.18, 0.78, 0.30 ] + }); + private static _secondarySelectionShader: WiredFilter = new WiredFilter({ + lineColor: [ 0.45, 0.78, 1 ], + color: [ 0.20, 0.52, 0.95 ] }); public static show(furniId: number): void { - WiredSelectionVisualizer.applySelectionShader(WiredSelectionVisualizer.getRoomObject(furniId)); + WiredSelectionVisualizer.applySelectionShader(WiredSelectionVisualizer.getRoomObject(furniId), WiredSelectionVisualizer._selectionShader); } public static hide(furniId: number): void { - WiredSelectionVisualizer.clearSelectionShader(WiredSelectionVisualizer.getRoomObject(furniId)); + const roomObject = WiredSelectionVisualizer.getRoomObject(furniId); + + WiredSelectionVisualizer.clearSelectionShader(roomObject, WiredSelectionVisualizer._selectionShader); + WiredSelectionVisualizer.clearSelectionShader(roomObject, WiredSelectionVisualizer._secondarySelectionShader); + } + + public static showSecondary(furniId: number): void + { + WiredSelectionVisualizer.applySelectionShader(WiredSelectionVisualizer.getRoomObject(furniId), WiredSelectionVisualizer._secondarySelectionShader); + } + + public static hideSecondary(furniId: number): void + { + WiredSelectionVisualizer.clearSelectionShader(WiredSelectionVisualizer.getRoomObject(furniId), WiredSelectionVisualizer._secondarySelectionShader); } public static clearSelectionShaderFromFurni(furniIds: number[]): void { for(const furniId of furniIds) { - WiredSelectionVisualizer.clearSelectionShader(WiredSelectionVisualizer.getRoomObject(furniId)); + WiredSelectionVisualizer.clearSelectionShader(WiredSelectionVisualizer.getRoomObject(furniId), WiredSelectionVisualizer._selectionShader); } } @@ -29,7 +46,39 @@ export class WiredSelectionVisualizer { for(const furniId of furniIds) { - WiredSelectionVisualizer.applySelectionShader(WiredSelectionVisualizer.getRoomObject(furniId)); + WiredSelectionVisualizer.applySelectionShader(WiredSelectionVisualizer.getRoomObject(furniId), WiredSelectionVisualizer._selectionShader); + } + } + + public static clearSecondarySelectionShaderFromFurni(furniIds: number[]): void + { + for(const furniId of furniIds) + { + WiredSelectionVisualizer.clearSelectionShader(WiredSelectionVisualizer.getRoomObject(furniId), WiredSelectionVisualizer._secondarySelectionShader); + } + } + + public static applySecondarySelectionShaderToFurni(furniIds: number[]): void + { + for(const furniId of furniIds) + { + WiredSelectionVisualizer.applySelectionShader(WiredSelectionVisualizer.getRoomObject(furniId), WiredSelectionVisualizer._secondarySelectionShader); + } + } + + public static clearAllSelectionShaders(): void + { + const roomEngine = GetRoomEngine(); + const roomId = roomEngine.activeRoomId; + + if(roomId < 0) return; + + const roomObjects = roomEngine.getRoomObjects(roomId, RoomObjectCategory.FLOOR); + + for(const roomObject of roomObjects) + { + WiredSelectionVisualizer.clearSelectionShader(roomObject, WiredSelectionVisualizer._selectionShader); + WiredSelectionVisualizer.clearSelectionShader(roomObject, WiredSelectionVisualizer._secondarySelectionShader); } } @@ -40,7 +89,7 @@ export class WiredSelectionVisualizer return roomEngine.getRoomObject(roomEngine.activeRoomId, objectId, RoomObjectCategory.FLOOR); } - private static applySelectionShader(roomObject: IRoomObject): void + private static applySelectionShader(roomObject: IRoomObject, filter: WiredFilter): void { if(!roomObject) return; @@ -54,13 +103,15 @@ export class WiredSelectionVisualizer if(!sprite.filters) sprite.filters = []; - sprite.filters.push(WiredSelectionVisualizer._selectionShader); + if(sprite.filters.includes(filter)) continue; + + sprite.filters.push(filter); sprite.increaseUpdateCounter(); } } - private static clearSelectionShader(roomObject: IRoomObject): void + private static clearSelectionShader(roomObject: IRoomObject, filter: WiredFilter): void { if(!roomObject) return; @@ -72,7 +123,7 @@ export class WiredSelectionVisualizer { if(!sprite.filters) continue; - const index = sprite.filters.indexOf(WiredSelectionVisualizer._selectionShader); + const index = sprite.filters.indexOf(filter); if(index >= 0) { diff --git a/src/api/wired/WiredTriggerLayoutCode.ts b/src/api/wired/WiredTriggerLayoutCode.ts index 683ff04..ac80e07 100644 --- a/src/api/wired/WiredTriggerLayoutCode.ts +++ b/src/api/wired/WiredTriggerLayoutCode.ts @@ -15,4 +15,10 @@ export class WiredTriggerLayout public static BOT_REACHED_STUFF: number = 13; public static BOT_REACHED_AVATAR: number = 14; public static RECEIVE_SIGNAL: number = 15; + public static AVATAR_LEAVES_ROOM: number = 16; + public static EXECUTE_PERIODICALLY_SHORT: number = 17; + public static CLICK_FURNI: number = 18; + public static CLICK_TILE: number = 19; + public static CLICK_USER: number = 20; + public static USER_PERFORMS_ACTION: number = 21; } diff --git a/src/assets/images/notifications/coolui.png b/src/assets/images/notifications/coolui.png deleted file mode 100644 index b78ce8b..0000000 Binary files a/src/assets/images/notifications/coolui.png and /dev/null differ diff --git a/src/assets/images/notifications/nitro_v3.png b/src/assets/images/notifications/nitro_v3.png new file mode 100644 index 0000000..5a30d56 Binary files /dev/null and b/src/assets/images/notifications/nitro_v3.png differ diff --git a/src/common/Button.tsx b/src/common/Button.tsx index 4da0794..6b7d454 100644 --- a/src/common/Button.tsx +++ b/src/common/Button.tsx @@ -12,16 +12,20 @@ export interface ButtonProps extends FlexProps export const Button: FC = props => { - const { variant = 'primary', size = 'sm', active = false, disabled = false, classNames = [], style = {}, ...rest } = props; + const { variant = 'primary', size = 'sm', active = false, disabled = false, classNames = [], ...rest } = props; const getClassNames = useMemo(() => { + + // fucked up method i know (i dont have a clue what im doing because im a ninja) + const newClassNames: string[] = [ 'pointer-events-auto inline-block font-normal leading-normal text-[#fff] text-center no-underline align-middle cursor-pointer select-none border border-[solid] border-transparent px-[.75rem] py-[.375rem] text-[.9rem] rounded-[.25rem] [transition:color_.15s_ease-in-out,background-color_.15s_ease-in-out,border-color_.15s_ease-in-out,box-shadow_.15s_ease-in-out]' ]; if(variant) { + if(variant == 'primary') - newClassNames.push('text-white [box-shadow:inset_0_2px_#ffffff26,inset_0_-2px_#0000001a,0_1px_#0000001a] hover:text-white'); + newClassNames.push('text-white bg-[#1e7295] border-[#1e7295] [box-shadow:inset_0_2px_#ffffff26,inset_0_-2px_#0000001a,0_1px_#0000001a] hover:text-white hover:bg-[#1a617f] hover:border-[#185b77]'); if(variant == 'success') newClassNames.push('text-white bg-[#00800b] border-[#00800b] [box-shadow:inset_0_2px_#ffffff26,inset_0_-2px_#0000001a,0_1px_#0000001a] hover:text-white hover:bg-[#006d09] hover:border-[#006609]'); @@ -39,10 +43,11 @@ export const Button: FC = props => newClassNames.push('text-white bg-[#185d79] border-[#185d79] [box-shadow:inset_0_2px_#ffffff26,inset_0_-2px_#0000001a,0_1px_#0000001a] hover:text-white hover:bg-[#144f67] hover:border-[#134a61]'); if(variant == 'dark') - newClassNames.push('text-white [box-shadow:inset_0_2px_#ffffff26,inset_0_-2px_#0000001a,0_1px_#0000001a] hover:text-white'); - + newClassNames.push('text-white bg-dark [box-shadow:inset_0_2px_#ffffff26,inset_0_-2px_#0000001a,0_1px_#0000001a] hover:text-white hover:bg-[#18181bfb] hover:border-[#161619fb]'); + if(variant == 'gray') - newClassNames.push('text-white [box-shadow:inset_0_2px_#ffffff26,inset_0_-2px_#0000001a,0_1px_#0000001a] hover:text-white'); + newClassNames.push('text-white bg-[#1e7295] border-[#1e7295] [box-shadow:inset_0_2px_#ffffff26,inset_0_-2px_#0000001a,0_1px_#0000001a] hover:text-white hover:bg-[#1a617f] hover:border-[#185b77]'); + } if(size) @@ -62,28 +67,5 @@ export const Button: FC = props => return newClassNames; }, [ variant, size, active, disabled, classNames ]); - const getStyle = useMemo(() => - { - if(variant === 'primary' || variant === 'gray') - { - return { - backgroundColor: 'var(--ui-btn-primary-bg, #1e7295)', - borderColor: 'var(--ui-btn-primary-border, #1e7295)', - ...style - }; - } - - if(variant === 'dark') - { - return { - backgroundColor: 'var(--ui-dark-bg, rgba(28, 28, 32, .98))', - borderColor: 'var(--ui-dark-border, rgba(28, 28, 32, .98))', - ...style - }; - } - - return style; - }, [ variant, style ]); - - return ; + return ; }; diff --git a/src/common/Slider.tsx b/src/common/Slider.tsx index 50cba28..206c0a5 100644 --- a/src/common/Slider.tsx +++ b/src/common/Slider.tsx @@ -11,11 +11,25 @@ export interface SliderProps extends ReactSliderProps export const Slider: FC = props => { - const { disabledButton, max, min, value, onChange, ...rest } = props; + const { disabledButton, max, min, step, value, onChange, ...rest } = props; + const currentValue = Array.isArray(value) ? value[0] : ((typeof value === 'number') ? value : 0); + const minimum = (typeof min === 'number') ? min : 0; + const maximum = (typeof max === 'number') ? max : 0; + const buttonStep = ((typeof step === 'number') && (step > 0)) ? step : 1; + + const roundToStep = (nextValue: number) => + { + if(typeof buttonStep !== 'number') return nextValue; + + const decimalStep = buttonStep.toString(); + const precision = decimalStep.includes('.') ? (decimalStep.length - decimalStep.indexOf('.') - 1) : 0; + + return parseFloat(nextValue.toFixed(precision)); + }; return - { !disabledButton && } - - { !disabledButton && } + { !disabledButton && } + + { !disabledButton && } ; } diff --git a/src/common/card/NitroCardHeaderView.tsx b/src/common/card/NitroCardHeaderView.tsx index 5bfbe2b..8bb354c 100644 --- a/src/common/card/NitroCardHeaderView.tsx +++ b/src/common/card/NitroCardHeaderView.tsx @@ -1,6 +1,5 @@ import { FC, MouseEvent } from 'react'; import { FaFlag } from 'react-icons/fa'; -import { useUiSettings } from '../../api'; import { Base, Column, ColumnProps, Flex } from '..'; interface NitroCardHeaderViewProps extends ColumnProps @@ -17,7 +16,8 @@ interface NitroCardHeaderViewProps extends ColumnProps export const NitroCardHeaderView: FC = props => { const { headerText = null, isGalleryPhoto = false, noCloseButton = false, isInfoToHabboPages = false, onReportPhoto = null, onClickInfoHabboPages = null, onCloseClick = null, justifyContent = 'center', alignItems = 'center', classNames = [], children = null, ...rest } = props; - const { isCustomActive, getHeaderStyle } = useUiSettings(); + + const onMouseDown = (event: MouseEvent) => { @@ -25,12 +25,8 @@ export const NitroCardHeaderView: FC = props => event.nativeEvent.stopImmediatePropagation(); }; - const headerClassName = isCustomActive - ? 'relative flex items-center justify-center flex-col drag-handler min-h-card-header max-h-card-header' - : 'relative flex items-center justify-center flex-col drag-handler min-h-card-header max-h-card-header bg-card-header'; - return ( - + { headerText } { isGalleryPhoto && diff --git a/src/common/card/tabs/NitroCardTabsView.tsx b/src/common/card/tabs/NitroCardTabsView.tsx index 5c49ac4..5e14506 100644 --- a/src/common/card/tabs/NitroCardTabsView.tsx +++ b/src/common/card/tabs/NitroCardTabsView.tsx @@ -1,27 +1,21 @@ import { FC, useMemo } from 'react'; -import { useUiSettings } from '../../../api'; import { Flex, FlexProps } from '../..'; export const NitroCardTabsView: FC = props => { const { justifyContent = 'center', gap = 1, classNames = [], children = null, ...rest } = props; - const { isCustomActive, getTabsStyle } = useUiSettings(); const getClassNames = useMemo(() => { - const base = isCustomActive - ? 'justify-center gap-0.5 flex min-h-card-tabs max-h-card-tabs pt-1 border-b border-card-border px-2 -mt-px' - : 'justify-center gap-0.5 flex bg-card-tabs min-h-card-tabs max-h-card-tabs pt-1 border-b border-card-border px-2 -mt-px'; - - const newClassNames: string[] = [ base ]; + const newClassNames: string[] = [ 'justify-center gap-0.5 flex bg-card-tabs min-h-card-tabs max-h-card-tabs pt-1 border-b border-card-border px-2 -mt-px' ]; if(classNames.length) newClassNames.push(...classNames); return newClassNames; - }, [ classNames, isCustomActive ]); + }, [ classNames ]); return ( - + { children } ); diff --git a/src/common/draggable-window/DraggableWindow.tsx b/src/common/draggable-window/DraggableWindow.tsx index 15c7fd0..7400a5f 100644 --- a/src/common/draggable-window/DraggableWindow.tsx +++ b/src/common/draggable-window/DraggableWindow.tsx @@ -8,6 +8,7 @@ const CURRENT_WINDOWS: HTMLElement[] = []; const POS_MEMORY: Map = new Map(); const BOUNDS_THRESHOLD_TOP: number = 0; const BOUNDS_THRESHOLD_LEFT: number = 0; +const DRAG_OUTSIDE_PERCENT: number = 0.80; export interface DraggableWindowProps { uniqueKey?: Key; @@ -80,8 +81,11 @@ export const DraggableWindow: FC = props => { const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; - const clampedX = Math.max(BOUNDS_THRESHOLD_LEFT, Math.min(newX, viewportWidth - windowWidth)); - const clampedY = Math.max(BOUNDS_THRESHOLD_TOP, Math.min(newY, viewportHeight - windowHeight)); + const maxOutX = windowWidth * DRAG_OUTSIDE_PERCENT; + const maxOutY = windowHeight * DRAG_OUTSIDE_PERCENT; + + const clampedX = Math.max(-maxOutX, Math.min(newX, viewportWidth - windowWidth + maxOutX)); + const clampedY = Math.max(-maxOutY, Math.min(newY, viewportHeight - windowHeight + maxOutY)); return { x: clampedX, y: clampedY }; }, []); diff --git a/src/common/layout/LayoutBadgeImageView.tsx b/src/common/layout/LayoutBadgeImageView.tsx index f1639c4..75a6533 100644 --- a/src/common/layout/LayoutBadgeImageView.tsx +++ b/src/common/layout/LayoutBadgeImageView.tsx @@ -67,11 +67,20 @@ export const LayoutBadgeImageView: FC = props => { if(event.badgeId !== badgeCode) return; - const element = await TextureUtils.generateImage(new NitroSprite(event.image)); - - console.log ('boe'); + if(isGroup) + { + const element = await TextureUtils.generateImage(new NitroSprite(event.image)); - element.onload = () => setImageElement(element); + element.onload = () => setImageElement(element); + } + else + { + const badgeUrl = GetConfigurationValue('badge.asset.url').replace('%badgename%', badgeCode.toString()); + const img = new Image(); + + img.onload = () => setImageElement(img); + img.src = badgeUrl; + } didSetBadge = true; @@ -84,13 +93,23 @@ export const LayoutBadgeImageView: FC = props => if(texture && !didSetBadge) { - (async () => + if(isGroup) { - const element = await TextureUtils.generateImage(new NitroSprite(texture)); - + (async () => + { + const element = await TextureUtils.generateImage(new NitroSprite(texture)); - element.onload = () => setImageElement(element); - })(); + element.onload = () => setImageElement(element); + })(); + } + else + { + const badgeUrl = GetConfigurationValue('badge.asset.url').replace('%badgename%', badgeCode.toString()); + const img = new Image(); + + img.onload = () => setImageElement(img); + img.src = badgeUrl; + } } return () => GetEventDispatcher().removeEventListener(BadgeImageReadyEvent.IMAGE_READY, onBadgeImageReadyEvent); diff --git a/src/components/MainView.tsx b/src/components/MainView.tsx index 41c320e..f04a072 100644 --- a/src/components/MainView.tsx +++ b/src/components/MainView.tsx @@ -1,4 +1,4 @@ -import { AddLinkEventTracker, GetCommunication, HabboWebTools, ILinkEventTracker, RemoveLinkEventTracker, RoomSessionEvent } from '@nitrots/nitro-renderer'; +import { AddLinkEventTracker, GetCommunication, GetRoomSessionManager, HabboWebTools, ILinkEventTracker, RemoveLinkEventTracker, RoomSessionEvent } from '@nitrots/nitro-renderer'; import { AnimatePresence, motion } from 'framer-motion'; import { FC, useEffect, useState } from 'react'; import { useNitroEvent } from '../hooks'; @@ -9,7 +9,6 @@ import { CampaignView } from './campaign/CampaignView'; import { CatalogView } from './catalog/CatalogView'; import { ChatHistoryView } from './chat-history/ChatHistoryView'; import { FloorplanEditorView } from './floorplan-editor/FloorplanEditorView'; -import { FurniEditorView } from './furni-editor/FurniEditorView'; import { FriendsView } from './friends/FriendsView'; import { GameCenterView } from './game-center/GameCenterView'; import { GroupsView } from './groups/GroupsView'; @@ -22,14 +21,13 @@ import { ModToolsView } from './mod-tools/ModToolsView'; import { NavigatorView } from './navigator/NavigatorView'; import { NitrobubbleHiddenView } from './nitrobubblehidden/NitrobubbleHiddenView'; import { NitropediaView } from './nitropedia/NitropediaView'; -import { ExternalPluginLoader } from './plugins/ExternalPluginLoader'; import { RightSideView } from './right-side/RightSideView'; import { RoomView } from './room/RoomView'; import { ToolbarView } from './toolbar/ToolbarView'; import { UserProfileView } from './user-profile/UserProfileView'; -import { InterfaceSettingsView } from './interface-settings/InterfaceSettingsView'; import { UserSettingsView } from './user-settings/UserSettingsView'; import { WiredView } from './wired/WiredView'; +import { WiredCreatorToolsView } from './wired-tools/WiredCreatorToolsView'; import { YoutubeTvView } from './youtube-tv/YoutubeTvView'; export const MainView: FC<{}> = props => @@ -44,6 +42,8 @@ export const MainView: FC<{}> = props => { setIsReady(true); + GetRoomSessionManager().tryRestoreSession(); + GetCommunication().connection.ready(); }, []); @@ -88,7 +88,6 @@ export const MainView: FC<{}> = props => { landingViewVisible && @@ -97,6 +96,7 @@ export const MainView: FC<{}> = props => + @@ -109,7 +109,6 @@ export const MainView: FC<{}> = props => - @@ -120,9 +119,7 @@ export const MainView: FC<{}> = props => - - ); }; diff --git a/src/components/camera/views/CameraWidgetCaptureView.tsx b/src/components/camera/views/CameraWidgetCaptureView.tsx index 4249d49..49e6d71 100644 --- a/src/components/camera/views/CameraWidgetCaptureView.tsx +++ b/src/components/camera/views/CameraWidgetCaptureView.tsx @@ -60,7 +60,7 @@ export const CameraWidgetCaptureView: FC = props = return ( - { selectedPicture && } + { selectedPicture && }
diff --git a/src/components/camera/views/editor/CameraWidgetEditorView.tsx b/src/components/camera/views/editor/CameraWidgetEditorView.tsx index d31ac35..b05da0e 100644 --- a/src/components/camera/views/editor/CameraWidgetEditorView.tsx +++ b/src/components/camera/views/editor/CameraWidgetEditorView.tsx @@ -1,8 +1,8 @@ -import { GetRoomCameraWidgetManager, IRoomCameraWidgetEffect, IRoomCameraWidgetSelectedEffect, NitroLogger, RoomCameraWidgetSelectedEffect } from '@nitrots/nitro-renderer'; +import { GetRoomCameraWidgetManager, IRoomCameraWidgetEffect, IRoomCameraWidgetSelectedEffect, NitroLogger, NitroTexture, RoomCameraWidgetSelectedEffect } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { FaSave, FaSearchMinus, FaSearchPlus, FaTrash } from 'react-icons/fa'; import { CameraEditorTabs, CameraPicture, CameraPictureThumbnail, LocalizeText } from '../../../../api'; -import { Button, Column, Flex, Grid, LayoutImage, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView, Slider, Text } from '../../../../common'; +import { Button, Column, Flex, Grid, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView, Slider, Text } from '../../../../common'; import { CameraWidgetEffectListView } from './effect-list'; export interface CameraWidgetEditorViewProps { @@ -23,10 +23,18 @@ export const CameraWidgetEditorView: FC = props => const [ selectedEffects, setSelectedEffects ] = useState([]); const [ effectsThumbnails, setEffectsThumbnails ] = useState([]); const [ isZoomed, setIsZoomed ] = useState(false); - const [ currentPictureUrl, setCurrentPictureUrl ] = useState(''); + const [ currentPictureUrl, setCurrentPictureUrl ] = useState(picture?.imageUrl ?? ''); + const [ stableTexture, setStableTexture ] = useState(null); const debounceTimerRef = useRef>(null); const requestIdRef = useRef(0); + useEffect(() => + { + const img = new Image(); + img.onload = () => setStableTexture(NitroTexture.from(img)); + img.src = picture.imageUrl; + }, [ picture ]); + const getColorMatrixEffects = useMemo(() => { return availableEffects.filter(effect => effect.colorMatrix); }, [ availableEffects ]); @@ -104,16 +112,27 @@ export const CameraWidgetEditorView: FC = props => return; } case 'clear_effects': - setSelectedEffectName(null); - setSelectedEffects([]); + onCancel(); return; case 'download': { - (async () => { - const image = new Image(); - image.src = currentPictureUrl; - const newWindow = window.open(''); - newWindow.document.write(image.outerHTML); - })(); + if(!currentPictureUrl) return; + + const parts = currentPictureUrl.split(','); + const mime = parts[0].match(/:(.*?);/)?.[1] || 'image/png'; + const binary = atob(parts[1]); + const bytes = new Uint8Array(binary.length); + for(let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i); + const blob = new Blob([ bytes ], { type: mime }); + const blobUrl = URL.createObjectURL(blob); + + const w = window.open('', '_blank'); + if(w) + { + w.document.title = 'camera_photo.png'; + w.document.body.style.margin = '0'; + w.document.body.innerHTML = ``; + } + return; } case 'zoom': @@ -123,25 +142,29 @@ export const CameraWidgetEditorView: FC = props => }, [ availableEffects, selectedEffectName, currentPictureUrl, getSelectedEffectIndex, onCancel, onCheckout, onClose ]); useEffect(() => { + if(!stableTexture) return; + const processThumbnails = async () => { const renderedEffects = await Promise.all( availableEffects.map(effect => - GetRoomCameraWidgetManager().applyEffects(picture.texture, [ new RoomCameraWidgetSelectedEffect(effect, 1) ], false) + GetRoomCameraWidgetManager().applyEffects(stableTexture, [ new RoomCameraWidgetSelectedEffect(effect, 1) ], false) ) ); setEffectsThumbnails(renderedEffects.map((image, index) => new CameraPictureThumbnail(availableEffects[index].name, image.src))); }; processThumbnails(); - }, [ picture, availableEffects ]); + }, [ stableTexture, availableEffects ]); useEffect(() => { + if(!stableTexture) return; + if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current); debounceTimerRef.current = setTimeout(() => { const id = ++requestIdRef.current; GetRoomCameraWidgetManager() - .applyEffects(picture.texture, selectedEffects, false) + .applyEffects(stableTexture, selectedEffects, false) .then(imageElement => { if (id !== requestIdRef.current) return; setCurrentPictureUrl(imageElement.src); @@ -152,7 +175,7 @@ export const CameraWidgetEditorView: FC = props => return () => { if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current); }; - }, [ picture, selectedEffects ]); + }, [ stableTexture, selectedEffects ]); return ( @@ -177,16 +200,14 @@ export const CameraWidgetEditorView: FC = props => - +
+ { currentPictureUrl && } +
{ selectedEffectName && ( { LocalizeText('camera.effect.name.' + selectedEffectName) } diff --git a/src/components/catalog/views/gift/CatalogGiftView.tsx b/src/components/catalog/views/gift/CatalogGiftView.tsx index c027fba..8104afb 100644 --- a/src/components/catalog/views/gift/CatalogGiftView.tsx +++ b/src/components/catalog/views/gift/CatalogGiftView.tsx @@ -7,6 +7,8 @@ import { CatalogEvent, CatalogInitGiftEvent, CatalogPurchasedEvent } from '../.. import { useCatalog, useFriends, useMessageEvent, useUiEvent } from '../../../../hooks'; import { classNames } from '../../../../layout'; +let isBuyingGift = false; + export const CatalogGiftView: FC<{}> = props => { const [ isVisible, setIsVisible ] = useState(false); @@ -32,6 +34,7 @@ export const CatalogGiftView: FC<{}> = props => const onClose = useCallback(() => { + isBuyingGift = false; setIsVisible(false); setPageId(0); setOfferId(0); @@ -122,6 +125,10 @@ export const CatalogGiftView: FC<{}> = props => return; } + if(isBuyingGift) return; + + isBuyingGift = true; + SendMessageComposer(new PurchaseFromCatalogAsGiftComposer(pageId, offerId, extraData, receiverName, message, colourId, selectedBoxIndex, selectedRibbonIndex, showMyFace)); return; } @@ -136,6 +143,7 @@ export const CatalogGiftView: FC<{}> = props => switch(event.type) { case CatalogPurchasedEvent.PURCHASE_SUCCESS: + isBuyingGift = false; onClose(); return; case CatalogEvent.INIT_GIFT: diff --git a/src/components/catalog/views/page/layout/CatalogLayoutRoomAdsView.tsx b/src/components/catalog/views/page/layout/CatalogLayoutRoomAdsView.tsx index 4a62f88..1722db8 100644 --- a/src/components/catalog/views/page/layout/CatalogLayoutRoomAdsView.tsx +++ b/src/components/catalog/views/page/layout/CatalogLayoutRoomAdsView.tsx @@ -6,6 +6,8 @@ import { useCatalog, useMessageEvent, useNavigator, useRoomPromote } from '../.. import { NitroInput } from '../../../../../layout'; import { CatalogLayoutProps } from './CatalogLayout.types'; +let isPurchasingAd = false; + export const CatalogLayoutRoomAdsView: FC = props => { const { page = null } = props; @@ -45,6 +47,10 @@ export const CatalogLayoutRoomAdsView: FC = props => const purchaseAd = () => { + if(isPurchasingAd) return; + + isPurchasingAd = true; + const pageId = page.pageId; const offerId = page.offers.length >= 1 ? page.offers[0].offerId : -1; const flatId = roomId; diff --git a/src/components/catalog/views/page/layout/CatalogLayoutVipBuyView.tsx b/src/components/catalog/views/page/layout/CatalogLayoutVipBuyView.tsx index 897fd58..1cb5283 100644 --- a/src/components/catalog/views/page/layout/CatalogLayoutVipBuyView.tsx +++ b/src/components/catalog/views/page/layout/CatalogLayoutVipBuyView.tsx @@ -1,5 +1,5 @@ import { ClubOfferData, GetClubOffersMessageComposer, PurchaseFromCatalogComposer } from '@nitrots/nitro-renderer'; -import { FC, useCallback, useEffect, useMemo, useState } from 'react'; +import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { CatalogPurchaseState, LocalizeText, SendMessageComposer } from '../../../../../api'; import { AutoGrid, Button, Column, Flex, Grid, LayoutCurrencyIcon, LayoutGridItem, LayoutLoadingSpinnerView, Text } from '../../../../../common'; import { CatalogEvent, CatalogPurchaseFailureEvent, CatalogPurchasedEvent } from '../../../../../events'; @@ -13,15 +13,18 @@ export const CatalogLayoutVipBuyView: FC = props => const { currentPage = null, catalogOptions = null } = useCatalog(); const { purse = null, getCurrencyAmount = null } = usePurse(); const { clubOffers = null } = catalogOptions; + const isPurchasingRef = useRef(false); const onCatalogEvent = useCallback((event: CatalogEvent) => { switch(event.type) { case CatalogPurchasedEvent.PURCHASE_SUCCESS: + isPurchasingRef.current = false; setPurchaseState(CatalogPurchaseState.NONE); return; case CatalogPurchaseFailureEvent.PURCHASE_FAILED: + isPurchasingRef.current = false; setPurchaseState(CatalogPurchaseState.FAILED); return; } @@ -83,8 +86,9 @@ export const CatalogLayoutVipBuyView: FC = props => const purchaseSubscription = useCallback(() => { - if(!pendingOffer) return; + if(!pendingOffer || isPurchasingRef.current) return; + isPurchasingRef.current = true; setPurchaseState(CatalogPurchaseState.PURCHASE); SendMessageComposer(new PurchaseFromCatalogComposer(currentPage.pageId, pendingOffer.offerId, null, 1)); }, [ pendingOffer, currentPage ]); diff --git a/src/components/catalog/views/page/layout/marketplace/CatalogLayoutMarketplaceOwnItemsView.tsx b/src/components/catalog/views/page/layout/marketplace/CatalogLayoutMarketplaceOwnItemsView.tsx index 3e1f847..4acd055 100644 --- a/src/components/catalog/views/page/layout/marketplace/CatalogLayoutMarketplaceOwnItemsView.tsx +++ b/src/components/catalog/views/page/layout/marketplace/CatalogLayoutMarketplaceOwnItemsView.tsx @@ -1,5 +1,5 @@ import { CancelMarketplaceOfferMessageComposer, GetMarketplaceOwnOffersMessageComposer, MarketplaceCancelOfferResultEvent, MarketplaceOwnOffersEvent, RedeemMarketplaceOfferCreditsMessageComposer } from '@nitrots/nitro-renderer'; -import { FC, useCallback, useEffect, useMemo, useState } from 'react'; +import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { LocalizeText, MarketplaceOfferData, MarketPlaceOfferState, NotificationAlertType, SendMessageComposer } from '../../../../../../api'; import { Button, Column, Text } from '../../../../../../common'; import { useMessageEvent, useNotification } from '../../../../../../hooks'; @@ -11,6 +11,8 @@ export const CatalogLayoutMarketplaceOwnItemsView: FC = prop const [ creditsWaiting, setCreditsWaiting ] = useState(0); const [ offers, setOffers ] = useState([]); const { simpleAlert = null } = useNotification(); + const isRedeemingRef = useRef(false); + const pendingCancelsRef = useRef>(new Set()); useMessageEvent(MarketplaceOwnOffersEvent, event => { @@ -54,6 +56,10 @@ export const CatalogLayoutMarketplaceOwnItemsView: FC = prop const redeemSoldOffers = useCallback(() => { + if(isRedeemingRef.current) return; + + isRedeemingRef.current = true; + setOffers(prevValue => { const idsToDelete = soldOffers.map(value => value.offerId); @@ -62,11 +68,19 @@ export const CatalogLayoutMarketplaceOwnItemsView: FC = prop }); SendMessageComposer(new RedeemMarketplaceOfferCreditsMessageComposer()); + + setTimeout(() => isRedeemingRef.current = false, 3000); }, [ soldOffers ]); const takeItemBack = (offerData: MarketplaceOfferData) => { + if(pendingCancelsRef.current.has(offerData.offerId)) return; + + pendingCancelsRef.current.add(offerData.offerId); + SendMessageComposer(new CancelMarketplaceOfferMessageComposer(offerData.offerId)); + + setTimeout(() => pendingCancelsRef.current.delete(offerData.offerId), 2000); }; useEffect(() => diff --git a/src/components/catalog/views/page/layout/marketplace/CatalogLayoutMarketplacePublicItemsView.tsx b/src/components/catalog/views/page/layout/marketplace/CatalogLayoutMarketplacePublicItemsView.tsx index b95d948..459f2ce 100644 --- a/src/components/catalog/views/page/layout/marketplace/CatalogLayoutMarketplacePublicItemsView.tsx +++ b/src/components/catalog/views/page/layout/marketplace/CatalogLayoutMarketplacePublicItemsView.tsx @@ -1,5 +1,5 @@ import { BuyMarketplaceOfferMessageComposer, GetMarketplaceOffersMessageComposer, MarketplaceBuyOfferResultEvent, MarketPlaceOffersEvent } from '@nitrots/nitro-renderer'; -import { FC, useCallback, useMemo, useState } from 'react'; +import { FC, useCallback, useMemo, useRef, useState } from 'react'; import { IMarketplaceSearchOptions, LocalizeText, MarketplaceOfferData, MarketplaceSearchType, NotificationAlertType, SendMessageComposer } from '../../../../../../api'; import { Button, Column, Text } from '../../../../../../common'; import { useMessageEvent, useNotification, usePurse } from '../../../../../../hooks'; @@ -23,6 +23,7 @@ export const CatalogLayoutMarketplacePublicItemsView: FC({ minPrice: -1, maxPrice: -1, query: '', type: 3 }); const { getCurrencyAmount = null } = usePurse(); const { simpleAlert = null, showConfirm = null } = useNotification(); + const isBuyingRef = useRef(false); const requestOffers = useCallback((options: IMarketplaceSearchOptions) => { @@ -56,6 +57,9 @@ export const CatalogLayoutMarketplacePublicItemsView: FC { + if(isBuyingRef.current) return; + + isBuyingRef.current = true; SendMessageComposer(new BuyMarketplaceOfferMessageComposer(offerId)); }, null, null, null, LocalizeText('catalog.marketplace.confirm_title')); @@ -83,6 +87,8 @@ export const CatalogLayoutMarketplacePublicItemsView: FC = props => { const [ item, setItem ] = useState(null); @@ -65,10 +67,15 @@ export const MarketplacePostOfferView: FC<{}> = props => const postItem = () => { - if(!item || (askingPrice < marketplaceConfiguration.minimumPrice)) return; + if(!item || (askingPrice < marketplaceConfiguration.minimumPrice) || isPostingMarketplaceOffer) return; showConfirm(LocalizeText('inventory.marketplace.confirm_offer.info', [ 'furniname', 'price' ], [ getFurniTitle, askingPrice.toString() ]), () => { + if(isPostingMarketplaceOffer) return; + + isPostingMarketplaceOffer = true; + setTimeout(() => isPostingMarketplaceOffer = false, 5000); + SendMessageComposer(new MakeOfferMessageComposer(askingPrice, item.isWallItem ? 2 : 1, item.id)); setItem(null); }, diff --git a/src/components/catalog/views/page/layout/vip-gifts/CatalogLayoutVipGiftsView.tsx b/src/components/catalog/views/page/layout/vip-gifts/CatalogLayoutVipGiftsView.tsx index 02daa03..f627250 100644 --- a/src/components/catalog/views/page/layout/vip-gifts/CatalogLayoutVipGiftsView.tsx +++ b/src/components/catalog/views/page/layout/vip-gifts/CatalogLayoutVipGiftsView.tsx @@ -6,6 +6,8 @@ import { useCatalog, useNotification, usePurse } from '../../../../../../hooks'; import { CatalogLayoutProps } from '../CatalogLayout.types'; import { VipGiftItem } from './VipGiftItemView'; +let isSelectingGift = false; + export const CatalogLayoutVipGiftsView: FC = props => { const { purse = null } = usePurse(); @@ -30,6 +32,10 @@ export const CatalogLayoutVipGiftsView: FC = props => { showConfirm(LocalizeText('catalog.club_gift.confirm'), () => { + if(isSelectingGift) return; + + isSelectingGift = true; + SendMessageComposer(new SelectClubGiftComposer(localizationId)); setCatalogOptions(prevValue => @@ -38,6 +44,8 @@ export const CatalogLayoutVipGiftsView: FC = props => return { ...prevValue }; }); + + setTimeout(() => isSelectingGift = false, 5000); }, null); }, [ setCatalogOptions, showConfirm ]); diff --git a/src/components/catalog/views/page/widgets/CatalogPurchaseWidgetView.tsx b/src/components/catalog/views/page/widgets/CatalogPurchaseWidgetView.tsx index 25cf610..7f2837d 100644 --- a/src/components/catalog/views/page/widgets/CatalogPurchaseWidgetView.tsx +++ b/src/components/catalog/views/page/widgets/CatalogPurchaseWidgetView.tsx @@ -11,6 +11,8 @@ interface CatalogPurchaseWidgetViewProps purchaseCallback?: () => void; } +let isPurchasingCatalogItem = false; + export const CatalogPurchaseWidgetView: FC = props => { const { noGiftOption = false, purchaseCallback = null } = props; @@ -25,15 +27,19 @@ export const CatalogPurchaseWidgetView: FC = pro switch(event.type) { case CatalogPurchasedEvent.PURCHASE_SUCCESS: + isPurchasingCatalogItem = false; setPurchaseState(CatalogPurchaseState.NONE); return; case CatalogPurchaseFailureEvent.PURCHASE_FAILED: + isPurchasingCatalogItem = false; setPurchaseState(CatalogPurchaseState.FAILED); return; case CatalogPurchaseNotAllowedEvent.NOT_ALLOWED: + isPurchasingCatalogItem = false; setPurchaseState(CatalogPurchaseState.FAILED); return; case CatalogPurchaseSoldOutEvent.SOLD_OUT: + isPurchasingCatalogItem = false; setPurchaseState(CatalogPurchaseState.SOLD_OUT); return; } @@ -62,7 +68,7 @@ export const CatalogPurchaseWidgetView: FC = pro const purchase = (isGift: boolean = false) => { - if(!currentOffer) return; + if(!currentOffer || isPurchasingCatalogItem) return; if(GetClubMemberLevel() < currentOffer.clubLevel) { @@ -78,6 +84,7 @@ export const CatalogPurchaseWidgetView: FC = pro return; } + isPurchasingCatalogItem = true; setPurchaseState(CatalogPurchaseState.PURCHASE); if(purchaseCallback) diff --git a/src/components/catalog/views/targeted-offer/OfferWindowView.tsx b/src/components/catalog/views/targeted-offer/OfferWindowView.tsx index e3052ed..0d00732 100644 --- a/src/components/catalog/views/targeted-offer/OfferWindowView.tsx +++ b/src/components/catalog/views/targeted-offer/OfferWindowView.tsx @@ -4,6 +4,8 @@ import { FriendlyTime, GetConfigurationValue, LocalizeText, SendMessageComposer import { Button, Column, Flex, LayoutCurrencyIcon, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common'; import { usePurse } from '../../../../hooks'; +let isBuyingOffer = false; + export const OfferWindowView = (props: { offer: TargetedOfferData, setOpen: Dispatch> }) => { const { offer = null, setOpen = null } = props; @@ -37,8 +39,14 @@ export const OfferWindowView = (props: { offer: TargetedOfferData, setOpen: Disp const buyOffer = () => { + if(isBuyingOffer) return; + + isBuyingOffer = true; + SendMessageComposer(new PurchaseTargetedOfferComposer(offer.id, amount)); SendMessageComposer(new GetTargetedOfferComposer()); + + setTimeout(() => isBuyingOffer = false, 5000); }; if(!offer) return; diff --git a/src/components/floorplan-editor/FloorplanEditorContext.tsx b/src/components/floorplan-editor/FloorplanEditorContext.tsx index 1b2a3c4..eb528cf 100644 --- a/src/components/floorplan-editor/FloorplanEditorContext.tsx +++ b/src/components/floorplan-editor/FloorplanEditorContext.tsx @@ -8,25 +8,13 @@ interface IFloorplanEditorContext setOriginalFloorplanSettings: Dispatch>; visualizationSettings: IVisualizationSettings; setVisualizationSettings: Dispatch>; - floorHeight: number; - setFloorHeight: Dispatch>; - floorAction: number; - setFloorAction: Dispatch>; - tilemapVersion: number; - areaInfo: { total: number; walkable: number }; } const FloorplanEditorContext = createContext({ originalFloorplanSettings: null, setOriginalFloorplanSettings: null, visualizationSettings: null, - setVisualizationSettings: null, - floorHeight: 0, - setFloorHeight: null, - floorAction: 3, - setFloorAction: null, - tilemapVersion: 0, - areaInfo: { total: 0, walkable: 0 } + setVisualizationSettings: null }); export const FloorplanEditorContextProvider: FC> = props => ; diff --git a/src/components/floorplan-editor/FloorplanEditorView.tsx b/src/components/floorplan-editor/FloorplanEditorView.tsx index 4003d13..59f7709 100644 --- a/src/components/floorplan-editor/FloorplanEditorView.tsx +++ b/src/components/floorplan-editor/FloorplanEditorView.tsx @@ -1,22 +1,19 @@ import { AddLinkEventTracker, FloorHeightMapEvent, ILinkEventTracker, RemoveLinkEventTracker, RoomEngineEvent, RoomVisualizationSettingsEvent, UpdateFloorPropertiesMessageComposer } from '@nitrots/nitro-renderer'; -import { FC, useCallback, useEffect, useState } from 'react'; -import { FaCaretLeft, FaCaretRight } from 'react-icons/fa'; +import { FC, useEffect, useState } from 'react'; import { LocalizeText, SendMessageComposer } from '../../api'; -import { Button, ButtonGroup, Column, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../common'; +import { Button, ButtonGroup, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../common'; import { useMessageEvent, useNitroEvent } from '../../hooks'; import { FloorplanEditorContextProvider } from './FloorplanEditorContext'; import { FloorplanEditor } from '@nitrots/nitro-renderer'; import { IFloorplanSettings } from '@nitrots/nitro-renderer'; import { IVisualizationSettings } from '@nitrots/nitro-renderer'; -import { convertNumbersForSaving, convertSettingToNumber, FloorAction, HEIGHT_SCHEME } from '@nitrots/nitro-renderer'; +import { convertNumbersForSaving, convertSettingToNumber } from '@nitrots/nitro-renderer'; import { FloorplanCanvasView } from './views/FloorplanCanvasView'; import { FloorplanImportExportView } from './views/FloorplanImportExportView'; import { FloorplanOptionsView } from './views/FloorplanOptionsView'; -import { FloorplanHeightSelector } from './views/FloorplanHeightSelector'; -import { FloorplanPreviewView } from './views/FloorplanPreviewView'; -const MIN_WALL_HEIGHT = 0; -const MAX_WALL_HEIGHT = 16; + +type ScrollDirection = 'up' | 'down' | 'left' | 'right'; export const FloorplanEditorView: FC<{}> = props => { @@ -37,65 +34,7 @@ export const FloorplanEditorView: FC<{}> = props => thicknessWall: 1, thicknessFloor: 1 }); - const [ floorHeight, setFloorHeight ] = useState(0); - const [ floorAction, setFloorAction ] = useState(FloorAction.SET); - const [ tilemapVersion, setTilemapVersion ] = useState(0); - const [ areaInfo, setAreaInfo ] = useState({ total: 0, walkable: 0 }); - - const calculateArea = useCallback(() => - { - const tilemap = FloorplanEditor.instance.tilemap; - - if(!tilemap || tilemap.length === 0) - { - setAreaInfo({ total: 0, walkable: 0 }); - - return; - } - - let total = 0; - let walkable = 0; - - for(let y = 0; y < tilemap.length; y++) - { - if(!tilemap[y]) continue; - - for(let x = 0; x < tilemap[y].length; x++) - { - if(!tilemap[y][x] || tilemap[y][x].height === 'x') continue; - - total++; - - if(!tilemap[y][x].isBlocked) walkable++; - } - } - - setAreaInfo({ total, walkable }); - }, []); - - // sync floorHeight/floorAction changes to the FloorplanEditor instance - useEffect(() => - { - FloorplanEditor.instance.actionSettings.currentAction = floorAction; - FloorplanEditor.instance.actionSettings.currentHeight = floorHeight.toString(36); - }, [ floorHeight, floorAction ]); - - // register onTilemapChange callback - useEffect(() => - { - if(!isVisible) return; - - FloorplanEditor.instance.onTilemapChange = () => - { - setTilemapVersion(prev => prev + 1); - calculateArea(); - }; - - return () => - { - FloorplanEditor.instance.onTilemapChange = null; - }; - }, [ isVisible, calculateArea ]); + const [ canvasScrollHandler, setCanvasScrollHandler ] = useState<((direction: ScrollDirection) => void) | null>(null); const saveFloorChanges = () => { @@ -108,50 +47,16 @@ export const FloorplanEditorView: FC<{}> = props => convertNumbersForSaving(visualizationSettings.thicknessFloor), (visualizationSettings.wallHeight - 1) )); - }; + } const revertChanges = () => { setVisualizationSettings({ wallHeight: originalFloorplanSettings.wallHeight, thicknessWall: originalFloorplanSettings.thicknessWall, thicknessFloor: originalFloorplanSettings.thicknessFloor, entryPointDir: originalFloorplanSettings.entryPointDir }); - + FloorplanEditor.instance.doorLocation = { x: originalFloorplanSettings.entryPoint[0], y: originalFloorplanSettings.entryPoint[1] }; FloorplanEditor.instance.setTilemap(originalFloorplanSettings.tilemap, originalFloorplanSettings.reservedTiles); FloorplanEditor.instance.renderTiles(); - }; - - const onWallHeightChange = (value: number) => - { - if(isNaN(value) || (value <= 0)) value = MIN_WALL_HEIGHT; - - if(value > MAX_WALL_HEIGHT) value = MAX_WALL_HEIGHT; - - setVisualizationSettings(prevValue => - { - const newValue = { ...prevValue }; - - newValue.wallHeight = value; - - return newValue; - }); - }; - - const increaseWallHeight = () => - { - let height = (visualizationSettings.wallHeight + 1); - - if(height > MAX_WALL_HEIGHT) height = MAX_WALL_HEIGHT; - - onWallHeightChange(height); - }; - - const decreaseWallHeight = () => - { - let height = (visualizationSettings.wallHeight - 1); - - if(height <= 0) height = MIN_WALL_HEIGHT; - - onWallHeightChange(height); - }; + } useNitroEvent(RoomEngineEvent.DISPOSED, event => setIsVisible(false)); @@ -212,7 +117,7 @@ export const FloorplanEditorView: FC<{}> = props => const parts = url.split('/'); if(parts.length < 2) return; - + switch(parts[1]) { case 'show': @@ -235,42 +140,17 @@ export const FloorplanEditorView: FC<{}> = props => }, []); return ( - + { isVisible && - + setIsVisible(false) } /> - - - - - - - - - { LocalizeText('floor.editor.wall.height') } - - onWallHeightChange(event.target.valueAsNumber) } /> - - - - Area: { areaInfo.total } ({ areaInfo.walkable } caselle) - - - + + canvasScrollHandler && canvasScrollHandler(direction) } /> + - + + @@ -281,4 +161,4 @@ export const FloorplanEditorView: FC<{}> = props => setImportExportVisible(false) } /> } ); -}; +} diff --git a/src/components/floorplan-editor/views/FloorplanCanvasView.tsx b/src/components/floorplan-editor/views/FloorplanCanvasView.tsx index 9db0903..e8f39a8 100644 --- a/src/components/floorplan-editor/views/FloorplanCanvasView.tsx +++ b/src/components/floorplan-editor/views/FloorplanCanvasView.tsx @@ -1,25 +1,25 @@ import { GetOccupiedTilesMessageComposer, GetRoomEntryTileMessageComposer, RoomEntryTileMessageEvent, RoomOccupiedTilesMessageEvent } from '@nitrots/nitro-renderer'; import { FC, useEffect, useRef, useState } from 'react'; -import { FaPlus, FaMinus } from 'react-icons/fa'; import { SendMessageComposer } from '../../../api'; import { Base, Column, ColumnProps } from '../../../common'; import { useMessageEvent } from '../../../hooks'; import { useFloorplanEditorContext } from '../FloorplanEditorContext'; import { FloorplanEditor } from '@nitrots/nitro-renderer'; +type ScrollDirection = 'up' | 'down' | 'left' | 'right'; + interface FloorplanCanvasViewProps extends ColumnProps { + setScrollHandler(handler: ((direction: ScrollDirection) => void) | null): void; } export const FloorplanCanvasView: FC = props => { - const { gap = 1, children = null, ...rest } = props; - const [ occupiedTilesReceived, setOccupiedTilesReceived ] = useState(false); + const { gap = 1, children = null, setScrollHandler = null, ...rest } = props; + const [ occupiedTilesReceived , setOccupiedTilesReceived ] = useState(false); const [ entryTileReceived, setEntryTileReceived ] = useState(false); - const [ zoomLevel, setZoomLevel ] = useState(1.0); const { originalFloorplanSettings = null, setOriginalFloorplanSettings = null, setVisualizationSettings = null } = useFloorplanEditorContext(); const elementRef = useRef(null); - const canvasWrapperRef = useRef(null); useMessageEvent(RoomOccupiedTilesMessageEvent, event => { @@ -37,7 +37,7 @@ export const FloorplanCanvasView: FC = props => }); setOccupiedTilesReceived(true); - + elementRef.current.scrollTo((FloorplanEditor.instance.renderer.canvas.width / 3), 0); }); @@ -63,16 +63,39 @@ export const FloorplanCanvasView: FC = props => return newValue; }); - + FloorplanEditor.instance.doorLocation = { x: parser.x, y: parser.y }; setEntryTileReceived(true); }); + const onClickArrowButton = (scrollDirection: ScrollDirection) => + { + const element = elementRef.current; + + if(!element) return; + + switch(scrollDirection) + { + case 'up': + element.scrollBy({ top: -10 }); + break; + case 'down': + element.scrollBy({ top: 10 }); + break; + case 'left': + element.scrollBy({ left: -10 }); + break; + case 'right': + element.scrollBy({ left: 10 }); + break; + } + } + const onPointerEvent = (event: PointerEvent) => { event.preventDefault(); - + switch(event.type) { case 'pointerout': @@ -86,10 +109,7 @@ export const FloorplanCanvasView: FC = props => FloorplanEditor.instance.onPointerMove(event); break; } - }; - - const zoomIn = () => setZoomLevel(prev => Math.min(prev + 0.25, 2.0)); - const zoomOut = () => setZoomLevel(prev => Math.max(prev - 0.25, 0.5)); + } useEffect(() => { @@ -104,15 +124,15 @@ export const FloorplanCanvasView: FC = props => thicknessWall: originalFloorplanSettings.thicknessWall, thicknessFloor: originalFloorplanSettings.thicknessFloor, entryPointDir: prevValue.entryPointDir - }; + } }); - }; + } }, [ originalFloorplanSettings.thicknessFloor, originalFloorplanSettings.thicknessWall, originalFloorplanSettings.wallHeight, setVisualizationSettings ]); useEffect(() => { if(!entryTileReceived || !occupiedTilesReceived) return; - + FloorplanEditor.instance.renderTiles(); }, [ entryTileReceived, occupiedTilesReceived ]); @@ -124,56 +144,45 @@ export const FloorplanCanvasView: FC = props => const currentElement = elementRef.current; if(!currentElement) return; - - const wrapper = canvasWrapperRef.current; - - if(wrapper) wrapper.appendChild(FloorplanEditor.instance.renderer.canvas); + + currentElement.appendChild(FloorplanEditor.instance.renderer.canvas); currentElement.addEventListener('pointerup', onPointerEvent); + currentElement.addEventListener('pointerout', onPointerEvent); + currentElement.addEventListener('pointerdown', onPointerEvent); + currentElement.addEventListener('pointermove', onPointerEvent); - return () => + return () => { if(currentElement) { currentElement.removeEventListener('pointerup', onPointerEvent); + currentElement.removeEventListener('pointerout', onPointerEvent); + currentElement.removeEventListener('pointerdown', onPointerEvent); + currentElement.removeEventListener('pointermove', onPointerEvent); } - }; + } }, []); + useEffect(() => + { + if(!setScrollHandler) return; + + setScrollHandler(() => onClickArrowButton); + + return () => setScrollHandler(null); + }, [ setScrollHandler ]); + return ( - - -
- -
- - -
+ + { children } ); -}; +} diff --git a/src/components/floorplan-editor/views/FloorplanHeightSelector.tsx b/src/components/floorplan-editor/views/FloorplanHeightSelector.tsx deleted file mode 100644 index 8163c98..0000000 --- a/src/components/floorplan-editor/views/FloorplanHeightSelector.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { FC } from 'react'; -import { COLORMAP, FloorAction, HEIGHT_SCHEME } from '@nitrots/nitro-renderer'; -import { FloorplanEditor } from '@nitrots/nitro-renderer'; -import { Column, Text } from '../../../common'; -import { useFloorplanEditorContext } from '../FloorplanEditorContext'; - -const colormap = COLORMAP as Record; - -export const FloorplanHeightSelector: FC<{}> = () => -{ - const { floorHeight, setFloorHeight, setFloorAction } = useFloorplanEditorContext(); - - const onSelectHeight = (height: number) => - { - setFloorHeight(height); - setFloorAction(FloorAction.SET); - - FloorplanEditor.instance.actionSettings.currentAction = FloorAction.SET; - FloorplanEditor.instance.actionSettings.currentHeight = height.toString(36); - }; - - const heights: number[] = []; - - for(let i = 26; i >= 0; i--) heights.push(i); - - return ( - - { floorHeight } -
- { heights.map(h => - { - const char = HEIGHT_SCHEME[h + 1]; - const color = colormap[char] || '101010'; - const isActive = (floorHeight === h); - - return ( -
onSelectHeight(h) } - title={ `${ h }` } - /> - ); - }) } -
- - ); -}; diff --git a/src/components/floorplan-editor/views/FloorplanOptionsView.tsx b/src/components/floorplan-editor/views/FloorplanOptionsView.tsx index d4e7705..5207b15 100644 --- a/src/components/floorplan-editor/views/FloorplanOptionsView.tsx +++ b/src/components/floorplan-editor/views/FloorplanOptionsView.tsx @@ -1,32 +1,45 @@ -import { FC } from 'react'; +import { FC, useState } from 'react'; +import { FaArrowDown, FaArrowLeft, FaArrowRight, FaArrowUp, FaCaretLeft, FaCaretRight } from 'react-icons/fa'; import { LocalizeText } from '../../../api'; -import { Flex, LayoutGridItem, Text } from '../../../common'; -import { FloorAction } from '@nitrots/nitro-renderer'; +import { Button, Column, Flex, LayoutGridItem, Slider, Text } from '../../../common'; +import { COLORMAP, FloorAction } from '@nitrots/nitro-renderer'; import { FloorplanEditor } from '@nitrots/nitro-renderer'; import { useFloorplanEditorContext } from '../FloorplanEditorContext'; +const MIN_WALL_HEIGHT: number = 0; +const MAX_WALL_HEIGHT: number = 16; + +const MIN_FLOOR_HEIGHT: number = 0; +const MAX_FLOOR_HEIGHT: number = 26; + +type ScrollDirection = 'up' | 'down' | 'left' | 'right'; + interface FloorplanOptionsViewProps { + onCanvasScroll?(direction: ScrollDirection): void; } export const FloorplanOptionsView: FC = props => { - const { visualizationSettings = null, setVisualizationSettings = null, floorAction, setFloorAction } = useFloorplanEditorContext(); - const isSquareSelectMode = FloorplanEditor.instance.isSquareSelectMode; - + const { onCanvasScroll = () => {} } = props; + const { visualizationSettings = null, setVisualizationSettings = null } = useFloorplanEditorContext(); + const [ floorAction, setFloorAction ] = useState(FloorAction.SET); + const [ floorHeight, setFloorHeight ] = useState(0); + const [ isSquareSelectMode, setSquareSelectMode ] = useState(false); + const selectAction = (action: number) => { setFloorAction(action); FloorplanEditor.instance.actionSettings.currentAction = action; - }; + } const toggleSquareSelectMode = () => { - FloorplanEditor.instance.toggleSquareSelectMode(); - // force re-render by toggling action to same value - setFloorAction(prev => prev); - }; + const nextValue = FloorplanEditor.instance.toggleSquareSelectMode(); + + setSquareSelectMode(nextValue); + } const changeDoorDirection = () => { @@ -45,19 +58,18 @@ export const FloorplanOptionsView: FC = props => return newValue; }); - }; + } - const onWallThicknessChange = (value: number) => + const onFloorHeightChange = (value: number) => { - setVisualizationSettings(prevValue => - { - const newValue = { ...prevValue }; + if(isNaN(value) || (value <= 0)) value = 0; - newValue.thicknessWall = value; + if(value > 26) value = 26; - return newValue; - }); - }; + setFloorHeight(value); + + FloorplanEditor.instance.actionSettings.currentHeight = value.toString(36); + } const onFloorThicknessChange = (value: number) => { @@ -69,54 +81,157 @@ export const FloorplanOptionsView: FC = props => return newValue; }); - }; + } + + const onWallThicknessChange = (value: number) => + { + setVisualizationSettings(prevValue => + { + const newValue = { ...prevValue }; + + newValue.thicknessWall = value; + + return newValue; + }); + } + + const onWallHeightChange = (value: number) => + { + if(isNaN(value) || (value <= 0)) value = MIN_WALL_HEIGHT; + + if(value > MAX_WALL_HEIGHT) value = MAX_WALL_HEIGHT; + + setVisualizationSettings(prevValue => + { + const newValue = { ...prevValue }; + + newValue.wallHeight = value; + + return newValue; + }); + } + + const increaseWallHeight = () => + { + let height = (visualizationSettings.wallHeight + 1); + + if(height > MAX_WALL_HEIGHT) height = MAX_WALL_HEIGHT; + + onWallHeightChange(height); + } + + const decreaseWallHeight = () => + { + let height = (visualizationSettings.wallHeight - 1); + + if(height <= 0) height = MIN_WALL_HEIGHT; + + onWallHeightChange(height); + } return ( - - - { LocalizeText('floor.plan.editor.draw.mode') } - - selectAction(FloorAction.SET) }> - - - selectAction(FloorAction.UNSET) }> - - - selectAction(FloorAction.UP) }> - - - selectAction(FloorAction.DOWN) }> - - - selectAction(FloorAction.DOOR) }> - - - FloorplanEditor.instance.toggleSelectAll() }> - - - - - - + + + + { LocalizeText('floor.plan.editor.draw.mode') } + + + selectAction(FloorAction.SET) }> + + + selectAction(FloorAction.UNSET) }> + + + + + selectAction(FloorAction.UP) }> + + + selectAction(FloorAction.DOWN) }> + + + + selectAction(FloorAction.DOOR) }> + + + FloorplanEditor.instance.toggleSelectAll() }> + + + + + + + + + { LocalizeText('floor.plan.editor.enter.direction') } + + + + { LocalizeText('floor.editor.wall.height') } + + + onWallHeightChange(event.target.valueAsNumber) } /> + + + + + { LocalizeText('floor.plan.editor.room.options') } + + + + + - - { LocalizeText('floor.plan.editor.enter.direction') } - + + + { LocalizeText('floor.plan.editor.tile.height') }: { floorHeight } +
+ onFloorHeightChange(event) } + renderThumb={ (props, state) => + { + const { key, style, ...rest } = (props as Record); + + return
{ state.valueNow }
; + } } /> +
+
+ + + + + + +
+ + + + + + - - - - - + ); -}; +} \ No newline at end of file diff --git a/src/components/floorplan-editor/views/FloorplanPreviewView.tsx b/src/components/floorplan-editor/views/FloorplanPreviewView.tsx deleted file mode 100644 index cd82a9c..0000000 --- a/src/components/floorplan-editor/views/FloorplanPreviewView.tsx +++ /dev/null @@ -1,328 +0,0 @@ -import { FC, useEffect, useRef } from 'react'; -import { COLORMAP, HEIGHT_SCHEME, FloorplanEditor } from '@nitrots/nitro-renderer'; -import { useFloorplanEditorContext } from '../FloorplanEditorContext'; - -const colormap = COLORMAP as Record; - -const PREVIEW_TILE_W = 16; -const PREVIEW_TILE_H = 8; -const PREVIEW_BLOCK_H = 5; -const WALL_HEIGHT_PX = 40; -const WALL_COLOR = '#6B7B5E'; -const WALL_SIDE_COLOR = '#5A6A4F'; -const WALL_TOP_COLOR = '#7D8E6F'; - -function hexToRgb(hex: string): [number, number, number] -{ - const r = parseInt(hex.substring(0, 2), 16); - const g = parseInt(hex.substring(2, 4), 16); - const b = parseInt(hex.substring(4, 6), 16); - - return [ r, g, b ]; -} - -function rgbToHex(r: number, g: number, b: number): string -{ - return `#${ ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1) }`; -} - -function darken(hex: string, factor: number): string -{ - const [ r, g, b ] = hexToRgb(hex); - - return rgbToHex( - Math.floor(r * factor), - Math.floor(g * factor), - Math.floor(b * factor) - ); -} - -function getTilemapBounds(tilemap: any[][]): { minX: number; minY: number; maxX: number; maxY: number } -{ - let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity; - - for(let y = 0; y < tilemap.length; y++) - { - if(!tilemap[y]) continue; - - for(let x = 0; x < tilemap[y].length; x++) - { - if(!tilemap[y][x] || tilemap[y][x].height === 'x') continue; - - if(x < minX) minX = x; - if(x > maxX) maxX = x; - if(y < minY) minY = y; - if(y > maxY) maxY = y; - } - } - - if(minX === Infinity) return { minX: 0, minY: 0, maxX: 0, maxY: 0 }; - - return { minX, minY, maxX, maxY }; -} - -function renderPreview(canvas: HTMLCanvasElement, wallHeight: number): void -{ - const ctx = canvas.getContext('2d'); - const tilemap = FloorplanEditor.instance.tilemap; - - if(!ctx || !tilemap || tilemap.length === 0) - { - if(ctx) - { - ctx.fillStyle = '#1a1a1a'; - ctx.fillRect(0, 0, canvas.width, canvas.height); - } - - return; - } - - const bounds = getTilemapBounds(tilemap); - const tilesW = bounds.maxX - bounds.minX + 1; - const tilesH = bounds.maxY - bounds.minY + 1; - - // find max height for offset calculation - let maxTileHeight = 0; - - for(let y = bounds.minY; y <= bounds.maxY; y++) - { - for(let x = bounds.minX; x <= bounds.maxX; x++) - { - if(!tilemap[y] || !tilemap[y][x] || tilemap[y][x].height === 'x') continue; - - const hi = HEIGHT_SCHEME.indexOf(tilemap[y][x].height) - 1; - - if(hi > maxTileHeight) maxTileHeight = hi; - } - } - - // calculate isometric bounds - const isoW = (tilesW + tilesH) * PREVIEW_TILE_W; - const isoH = (tilesW + tilesH) * PREVIEW_TILE_H + maxTileHeight * PREVIEW_BLOCK_H + WALL_HEIGHT_PX; - - // scale to fit canvas - const scaleX = (canvas.width - 20) / isoW; - const scaleY = (canvas.height - 20) / isoH; - const scale = Math.min(scaleX, scaleY, 3); - - const offsetX = (canvas.width - isoW * scale) / 2; - const offsetY = (canvas.height - isoH * scale) / 2 + WALL_HEIGHT_PX * scale * 0.5; - - ctx.fillStyle = '#1a1a1a'; - ctx.fillRect(0, 0, canvas.width, canvas.height); - - ctx.save(); - ctx.translate(offsetX, offsetY); - ctx.scale(scale, scale); - - const tw = PREVIEW_TILE_W; - const th = PREVIEW_TILE_H; - - function isoX(gx: number, gy: number): number - { - return (gx - bounds.minX - gy + bounds.minY) * tw + (tilesH - 1) * tw; - } - - function isoY(gx: number, gy: number): number - { - return (gx - bounds.minX + gy - bounds.minY) * th; - } - - function hasActiveTile(gx: number, gy: number): boolean - { - return tilemap[gy] && tilemap[gy][gx] && tilemap[gy][gx].height !== 'x'; - } - - function getTileHeight(gx: number, gy: number): number - { - if(!hasActiveTile(gx, gy)) return 0; - - return Math.max(0, HEIGHT_SCHEME.indexOf(tilemap[gy][gx].height) - 1); - } - - // draw walls on north and west edges - const wallH = wallHeight > 0 ? wallHeight * PREVIEW_BLOCK_H + WALL_HEIGHT_PX * 0.3 : WALL_HEIGHT_PX * 0.6; - - for(let y = bounds.minY; y <= bounds.maxY; y++) - { - for(let x = bounds.minX; x <= bounds.maxX; x++) - { - if(!hasActiveTile(x, y)) continue; - - const tileH = getTileHeight(x, y) * PREVIEW_BLOCK_H; - const cx = isoX(x, y); - const cy = isoY(x, y) - tileH; - - // west wall (no tile to the left) - if(!hasActiveTile(x - 1, y)) - { - ctx.beginPath(); - ctx.moveTo(cx, cy + th); - ctx.lineTo(cx, cy + th - wallH); - ctx.lineTo(cx + tw, cy - wallH); - ctx.lineTo(cx + tw, cy); - ctx.closePath(); - ctx.fillStyle = WALL_SIDE_COLOR; - ctx.fill(); - ctx.strokeStyle = '#4A5A3F'; - ctx.lineWidth = 0.5; - ctx.stroke(); - } - - // north wall (no tile above) - if(!hasActiveTile(x, y - 1)) - { - ctx.beginPath(); - ctx.moveTo(cx + tw, cy); - ctx.lineTo(cx + tw, cy - wallH); - ctx.lineTo(cx + tw * 2, cy + th - wallH); - ctx.lineTo(cx + tw * 2, cy + th); - ctx.closePath(); - ctx.fillStyle = WALL_COLOR; - ctx.fill(); - ctx.strokeStyle = '#4A5A3F'; - ctx.lineWidth = 0.5; - ctx.stroke(); - } - - // wall top cap - corner - if(!hasActiveTile(x - 1, y) && !hasActiveTile(x, y - 1)) - { - ctx.beginPath(); - ctx.moveTo(cx + tw, cy - wallH); - ctx.lineTo(cx + tw + tw * 0.3, cy - wallH - th * 0.3); - ctx.lineTo(cx + tw, cy - wallH - th * 0.6); - ctx.lineTo(cx + tw - tw * 0.3, cy - wallH - th * 0.3); - ctx.closePath(); - ctx.fillStyle = WALL_TOP_COLOR; - ctx.fill(); - } - } - } - - // draw tiles back-to-front - for(let y = bounds.minY; y <= bounds.maxY; y++) - { - for(let x = bounds.minX; x <= bounds.maxX; x++) - { - if(!hasActiveTile(x, y)) continue; - - const tile = tilemap[y][x]; - const heightIndex = HEIGHT_SCHEME.indexOf(tile.height) - 1; - const tileH = Math.max(0, heightIndex) * PREVIEW_BLOCK_H; - - const cx = isoX(x, y); - const cy = isoY(x, y) - tileH; - - const heightChar = tile.height; - const baseColor = colormap[heightChar] || 'aaaaaa'; - const topColor = `#${ baseColor }`; - const leftColor = darken(baseColor, 0.65); - const rightColor = darken(baseColor, 0.80); - - // draw side faces if tile has height - const blockH = Math.max(0, heightIndex) * PREVIEW_BLOCK_H; - - // left face (visible when no neighbor to south or neighbor is shorter) - const southH = getTileHeight(x, y + 1); - const leftExpose = hasActiveTile(x, y + 1) ? Math.max(0, heightIndex - southH) * PREVIEW_BLOCK_H : blockH + PREVIEW_BLOCK_H; - - if(leftExpose > 0) - { - ctx.beginPath(); - ctx.moveTo(cx, cy + th); - ctx.lineTo(cx + tw, cy + th * 2); - ctx.lineTo(cx + tw, cy + th * 2 + leftExpose); - ctx.lineTo(cx, cy + th + leftExpose); - ctx.closePath(); - ctx.fillStyle = leftColor; - ctx.fill(); - } - - // right face - const eastH = getTileHeight(x + 1, y); - const rightExpose = hasActiveTile(x + 1, y) ? Math.max(0, heightIndex - eastH) * PREVIEW_BLOCK_H : blockH + PREVIEW_BLOCK_H; - - if(rightExpose > 0) - { - ctx.beginPath(); - ctx.moveTo(cx + tw * 2, cy + th); - ctx.lineTo(cx + tw, cy + th * 2); - ctx.lineTo(cx + tw, cy + th * 2 + rightExpose); - ctx.lineTo(cx + tw * 2, cy + th + rightExpose); - ctx.closePath(); - ctx.fillStyle = rightColor; - ctx.fill(); - } - - // top face - ctx.beginPath(); - ctx.moveTo(cx + tw, cy); - ctx.lineTo(cx + tw * 2, cy + th); - ctx.lineTo(cx + tw, cy + th * 2); - ctx.lineTo(cx, cy + th); - ctx.closePath(); - ctx.fillStyle = topColor; - ctx.fill(); - - // door indicator - const door = FloorplanEditor.instance.doorLocation; - - if(door.x === x && door.y === y) - { - ctx.fillStyle = 'rgba(255, 255, 255, 0.5)'; - ctx.fill(); - ctx.strokeStyle = '#ffffff'; - ctx.lineWidth = 1; - ctx.stroke(); - } - } - } - - ctx.restore(); -} - -export const FloorplanPreviewView: FC<{}> = () => -{ - const { tilemapVersion, visualizationSettings } = useFloorplanEditorContext(); - const canvasRef = useRef(null); - const rafRef = useRef(0); - - useEffect(() => - { - if(!canvasRef.current) return; - - if(rafRef.current) cancelAnimationFrame(rafRef.current); - - rafRef.current = requestAnimationFrame(() => - { - const canvas = canvasRef.current; - - if(!canvas) return; - - const parent = canvas.parentElement; - - if(parent) - { - canvas.width = parent.clientWidth; - canvas.height = parent.clientHeight; - } - - renderPreview(canvas, visualizationSettings?.wallHeight ?? 0); - }); - - return () => - { - if(rafRef.current) cancelAnimationFrame(rafRef.current); - }; - }, [ tilemapVersion, visualizationSettings?.wallHeight ]); - - return ( -
- -
- ); -}; diff --git a/src/components/groups/views/GroupCreatorView.tsx b/src/components/groups/views/GroupCreatorView.tsx index 5bdc086..d0d24d1 100644 --- a/src/components/groups/views/GroupCreatorView.tsx +++ b/src/components/groups/views/GroupCreatorView.tsx @@ -15,6 +15,8 @@ interface GroupCreatorViewProps const TABS: number[] = [ 1, 2, 3, 4 ]; +let isBuyingGroup = false; + export const GroupCreatorView: FC = props => { const { onClose = null } = props; @@ -34,7 +36,10 @@ export const GroupCreatorView: FC = props => const buyGroup = () => { - if(!groupData) return; + if(!groupData || isBuyingGroup) return; + + isBuyingGroup = true; + setTimeout(() => isBuyingGroup = false, 5000); const badge = []; diff --git a/src/components/groups/views/GroupMembersView.tsx b/src/components/groups/views/GroupMembersView.tsx index 198c9ca..5adc6db 100644 --- a/src/components/groups/views/GroupMembersView.tsx +++ b/src/components/groups/views/GroupMembersView.tsx @@ -1,5 +1,5 @@ import { AddLinkEventTracker, GetSessionDataManager, GroupAdminGiveComposer, GroupAdminTakeComposer, GroupConfirmMemberRemoveEvent, GroupConfirmRemoveMemberComposer, GroupMemberParser, GroupMembersComposer, GroupMembersEvent, GroupMembershipAcceptComposer, GroupMembershipDeclineComposer, GroupMembersParser, GroupRank, GroupRemoveMemberComposer, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer'; -import { FC, useCallback, useEffect, useState } from 'react'; +import { FC, useCallback, useEffect, useRef, useState } from 'react'; import { FaChevronLeft, FaChevronRight } from 'react-icons/fa'; import { GetUserProfile, LocalizeText, SendMessageComposer } from '../../../api'; import { Button, Column, Flex, Grid, LayoutAvatarImageView, LayoutBadgeImageView, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../common'; @@ -16,6 +16,7 @@ export const GroupMembersView: FC<{}> = props => const [ searchQuery, setSearchQuery ] = useState(''); const [ removingMemberName, setRemovingMemberName ] = useState(null); const { showConfirm = null } = useNotification(); + const pendingActionsRef = useRef>(new Set()); const getRankDescription = (member: GroupMemberParser) => { @@ -42,6 +43,11 @@ export const GroupMembersView: FC<{}> = props => { if(!membersData.admin || (member.rank === GroupRank.OWNER)) return; + const key = `admin_${member.id}`; + if(pendingActionsRef.current.has(key)) return; + pendingActionsRef.current.add(key); + setTimeout(() => pendingActionsRef.current.delete(key), 2000); + if(member.rank !== GroupRank.ADMIN) SendMessageComposer(new GroupAdminGiveComposer(membersData.groupId, member.id)); else SendMessageComposer(new GroupAdminTakeComposer(membersData.groupId, member.id)); @@ -52,6 +58,11 @@ export const GroupMembersView: FC<{}> = props => { if(!membersData.admin || (member.rank !== GroupRank.REQUESTED)) return; + const key = `accept_${member.id}`; + if(pendingActionsRef.current.has(key)) return; + pendingActionsRef.current.add(key); + setTimeout(() => pendingActionsRef.current.delete(key), 2000); + SendMessageComposer(new GroupMembershipAcceptComposer(membersData.groupId, member.id)); refreshMembers(); @@ -61,6 +72,11 @@ export const GroupMembersView: FC<{}> = props => { if(!membersData.admin) return; + const key = `remove_${member.id}`; + if(pendingActionsRef.current.has(key)) return; + pendingActionsRef.current.add(key); + setTimeout(() => pendingActionsRef.current.delete(key), 2000); + if(member.rank === GroupRank.REQUESTED) { SendMessageComposer(new GroupMembershipDeclineComposer(membersData.groupId, member.id)); diff --git a/src/components/hotel-view/HotelView.tsx b/src/components/hotel-view/HotelView.tsx index efd3774..e0a2605 100644 --- a/src/components/hotel-view/HotelView.tsx +++ b/src/components/hotel-view/HotelView.tsx @@ -83,27 +83,36 @@ export const HotelView: FC<{}> = props => if(!container) return; - const viewportWidth = window.innerWidth; - const viewportHeight = window.innerHeight - 55; - - const lobbyEl = container.querySelector('.nitro-hotel-view-lobby'); - - if(lobbyEl) + const centerView = () => { - const containerRect = container.getBoundingClientRect(); - const lobbyRect = lobbyEl.getBoundingClientRect(); + const viewportWidth = window.innerWidth; + const viewportHeight = window.innerHeight - 55; - const lobbyCenterX = (lobbyRect.left - containerRect.left) + container.scrollLeft + lobbyRect.width / 2; - const lobbyCenterY = (lobbyRect.top - containerRect.top) + container.scrollTop + lobbyRect.height / 2; + const lobbyEl = container.querySelector('.nitro-hotel-view-lobby'); - container.scrollLeft = Math.max(0, lobbyCenterX - viewportWidth / 2); - container.scrollTop = Math.max(0, lobbyCenterY - viewportHeight / 2); - } - else - { - container.scrollLeft = Math.max(0, (2600 - viewportWidth) / 2); - container.scrollTop = Math.max(0, (1425 - viewportHeight) / 2); - } + if(lobbyEl) + { + const containerRect = container.getBoundingClientRect(); + const lobbyRect = lobbyEl.getBoundingClientRect(); + + const lobbyCenterX = (lobbyRect.left - containerRect.left) + container.scrollLeft + lobbyRect.width / 2; + const lobbyCenterY = (lobbyRect.top - containerRect.top) + container.scrollTop + lobbyRect.height / 2; + + container.scrollLeft = Math.max(0, lobbyCenterX - viewportWidth / 2); + container.scrollTop = Math.max(0, lobbyCenterY - viewportHeight / 2); + } + else + { + container.scrollLeft = Math.max(0, (2600 - viewportWidth) / 2); + container.scrollTop = Math.max(0, (1425 - viewportHeight) / 2); + } + }; + + centerView(); + + window.addEventListener('resize', centerView); + + return () => window.removeEventListener('resize', centerView); }, []); const handleMouseDown = (e: React.MouseEvent) => diff --git a/src/components/interface-settings/InterfaceColorTabView.tsx b/src/components/interface-settings/InterfaceColorTabView.tsx deleted file mode 100644 index 65bf172..0000000 --- a/src/components/interface-settings/InterfaceColorTabView.tsx +++ /dev/null @@ -1,179 +0,0 @@ -import { RgbaColorPicker, RgbaColor } from 'react-colorful'; -import { FC, useCallback, useMemo, useState } from 'react'; -import { FaUndo, FaTrash, FaPen, FaFillDrip, FaSave } from 'react-icons/fa'; -import { PRESET_COLORS, useUiSettings } from '../../api'; -import { Flex, Text } from '../../common'; - -const hexToRgba = (hex: string, a = 1): RgbaColor => -{ - const num = parseInt(hex.replace('#', ''), 16); - return { r: (num >> 16) & 0xFF, g: (num >> 8) & 0xFF, b: num & 0xFF, a }; -}; - -const rgbaToHex = (rgba: RgbaColor): string => -{ - return '#' + ((1 << 24) + (rgba.r << 16) + (rgba.g << 8) + rgba.b).toString(16).slice(1); -}; - -export const InterfaceColorTabView: FC<{}> = () => -{ - const { settings, updateSettings, resetSettings } = useUiSettings(); - const [ color, setColor ] = useState(() => hexToRgba(settings.headerColor, settings.headerAlpha / 100)); - - const hexColor = useMemo(() => rgbaToHex(color), [ color ]); - const alphaPercent = useMemo(() => Math.round((color.a ?? 1) * 100), [ color ]); - - const onHexInput = useCallback((value: string) => - { - const clean = value.replace(/[^0-9a-fA-F]/g, '').slice(0, 6); - if(clean.length === 6) - { - const rgba = hexToRgba('#' + clean, color.a); - setColor(rgba); - } - }, [ color.a ]); - - const onRgbInput = useCallback((channel: 'r' | 'g' | 'b', value: number) => - { - const clamped = Math.max(0, Math.min(255, value || 0)); - setColor(prev => ({ ...prev, [channel]: clamped })); - }, []); - - const onAlphaInput = useCallback((value: number) => - { - const clamped = Math.max(0, Math.min(100, value || 0)); - setColor(prev => ({ ...prev, a: clamped / 100 })); - }, []); - - const onPresetClick = useCallback((presetHex: string) => - { - setColor(hexToRgba(presetHex, color.a)); - }, [ color.a ]); - - const onSave = useCallback(() => - { - updateSettings({ - colorMode: 'color', - headerColor: hexColor, - headerAlpha: alphaPercent - }); - }, [ updateSettings, hexColor, alphaPercent ]); - - const onReset = useCallback(() => - { - resetSettings(); - setColor(hexToRgba('#1E7295', 1)); - }, [ resetSettings ]); - - const onDelete = useCallback(() => - { - updateSettings({ colorMode: 'default' }); - setColor(hexToRgba('#1E7295', 1)); - }, [ updateSettings ]); - - return ( - -
- -
- - - onHexInput(e.target.value) } - maxLength={ 6 } - /> - Hex - - - onRgbInput('r', parseInt(e.target.value)) } - min={ 0 } max={ 255 } - /> - R - - - onRgbInput('g', parseInt(e.target.value)) } - min={ 0 } max={ 255 } - /> - G - - - onRgbInput('b', parseInt(e.target.value)) } - min={ 0 } max={ 255 } - /> - B - - - onAlphaInput(parseInt(e.target.value)) } - min={ 0 } max={ 100 } - /> - A - - -
- { PRESET_COLORS.map((presetHex, i) => ( -
onPresetClick(presetHex) } - /> - )) } -
- - - - - - - - - ); -}; diff --git a/src/components/interface-settings/InterfaceImageTabView.tsx b/src/components/interface-settings/InterfaceImageTabView.tsx deleted file mode 100644 index 390a390..0000000 --- a/src/components/interface-settings/InterfaceImageTabView.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { FC, useCallback, useMemo } from 'react'; -import { GetConfigurationValue, useUiSettings } from '../../api'; - -export const InterfaceImageTabView: FC<{}> = () => -{ - const { settings, updateSettings } = useUiSettings(); - - const imageCount = useMemo(() => - { - return GetConfigurationValue('ui.header.images.count', 30); - }, []); - - const baseUrl = useMemo(() => - { - return GetConfigurationValue('ui.header.images.url', 'https://image.webbo.city/image/headerImage/image{id}.gif'); - }, []); - - const images = useMemo(() => - { - const result: string[] = []; - for(let i = 1; i <= imageCount; i++) - { - result.push(baseUrl.replace('{id}', String(i))); - } - return result; - }, [ imageCount, baseUrl ]); - - const onImageSelect = useCallback((url: string) => - { - updateSettings({ - colorMode: 'image', - headerImageUrl: url - }); - }, [ updateSettings ]); - - return ( -
- { images.map((url, i) => ( -
onImageSelect(url) } - /> - )) } -
- ); -}; diff --git a/src/components/interface-settings/InterfaceProfileTabView.tsx b/src/components/interface-settings/InterfaceProfileTabView.tsx deleted file mode 100644 index 6534c3c..0000000 --- a/src/components/interface-settings/InterfaceProfileTabView.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import { GetSessionDataManager, HabboClubLevelEnum } from '@nitrots/nitro-renderer'; -import { FC, useCallback, useMemo, useState } from 'react'; -import { GetClubMemberLevel, GetConfigurationValue } from '../../api'; -import { Base, Flex, Grid, LayoutCurrencyIcon, NitroCardTabsItemView, NitroCardTabsView, Text } from '../../common'; -import { useRoom } from '../../hooks'; - -interface ItemData -{ - id: number; - isHcOnly: boolean; - minRank: number; - isAmbassadorOnly: boolean; - selectable: boolean; -} - -const SUB_TABS = [ 'backgrounds', 'stands', 'overlays' ] as const; -type SubTabType = typeof SUB_TABS[number]; - -const SUB_TAB_LABELS: Record = { - backgrounds: 'Sfondi', - stands: 'Basi', - overlays: 'Overlay' -}; - -export const InterfaceProfileTabView: FC<{}> = () => -{ - const [ activeSubTab, setActiveSubTab ] = useState('backgrounds'); - const [ selectedBackground, setSelectedBackground ] = useState(0); - const [ selectedStand, setSelectedStand ] = useState(0); - const [ selectedOverlay, setSelectedOverlay ] = useState(0); - const { roomSession } = useRoom(); - - const userData = useMemo(() => ({ - isHcMember: GetClubMemberLevel() >= HabboClubLevelEnum.CLUB, - securityLevel: GetSessionDataManager().canChangeName, - isAmbassador: GetSessionDataManager().isAmbassador - }), []); - - const processData = useCallback((configData: any[], dataType: string): ItemData[] => - { - if(!configData?.length) return []; - - return configData - .filter(item => - { - const meetsRank = userData.securityLevel >= item.minRank; - const ambassadorEligible = !item.isAmbassadorOnly || userData.isAmbassador; - return item.isHcOnly || (meetsRank && ambassadorEligible); - }) - .map(item => ({ id: item[`${ dataType }Id`], ...item, selectable: !item.isHcOnly || userData.isHcMember })); - }, [ userData ]); - - const allData = useMemo(() => ({ - backgrounds: processData(GetConfigurationValue('backgrounds.data'), 'background'), - stands: processData(GetConfigurationValue('stands.data'), 'stand'), - overlays: processData(GetConfigurationValue('overlays.data'), 'overlay') - }), [ processData ]); - - const handleSelection = useCallback((id: number) => - { - if(!roomSession) return; - - const setters = { backgrounds: setSelectedBackground, stands: setSelectedStand, overlays: setSelectedOverlay }; - const currentValues = { backgrounds: selectedBackground, stands: selectedStand, overlays: selectedOverlay }; - - setters[activeSubTab](id); - const newValues = { ...currentValues, [activeSubTab]: id }; - roomSession.sendBackgroundMessage(newValues.backgrounds, newValues.stands, newValues.overlays); - }, [ activeSubTab, roomSession, selectedBackground, selectedStand, selectedOverlay ]); - - const renderItem = useCallback((item: ItemData, type: string) => ( - item.selectable && handleSelection(item.id) } - className={ item.selectable ? '' : 'non-selectable' } - > - - { item.isHcOnly && } - - ), [ handleSelection ]); - - return ( - - - { SUB_TABS.map(tab => ( - - )) } - - { !roomSession && ( - Entra in una stanza per modificare il profilo - ) } - { roomSession && ( - - { allData[activeSubTab].map(item => renderItem(item, activeSubTab.slice(0, -1))) } - - ) } - - ); -}; diff --git a/src/components/interface-settings/InterfaceSettingsView.tsx b/src/components/interface-settings/InterfaceSettingsView.tsx deleted file mode 100644 index cd465c7..0000000 --- a/src/components/interface-settings/InterfaceSettingsView.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { AddLinkEventTracker, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer'; -import { FC, useEffect, useState } from 'react'; -import { NitroCardContentView, NitroCardHeaderView, NitroCardTabsView, NitroCardTabsItemView, NitroCardView } from '../../common'; -import { InterfaceColorTabView } from './InterfaceColorTabView'; -import { InterfaceProfileTabView } from './InterfaceProfileTabView'; - -const TABS = [ 'color', 'profile' ] as const; -type TabType = typeof TABS[number]; - -const TAB_LABELS: Record = { - color: 'Colore', - profile: 'Sfondo profilo' -}; - -export const InterfaceSettingsView: FC<{}> = () => -{ - const [ isVisible, setIsVisible ] = useState(false); - const [ currentTab, setCurrentTab ] = useState('color'); - - useEffect(() => - { - const linkTracker: ILinkEventTracker = { - linkReceived: (url: string) => - { - const parts = url.split('/'); - if(parts.length < 2) return; - - switch(parts[1]) - { - case 'show': - setIsVisible(true); - return; - case 'hide': - setIsVisible(false); - return; - case 'toggle': - setIsVisible(prev => !prev); - return; - case 'profile': - setCurrentTab('profile'); - setIsVisible(true); - return; - } - }, - eventUrlPrefix: 'interface-settings/' - }; - - AddLinkEventTracker(linkTracker); - return () => RemoveLinkEventTracker(linkTracker); - }, []); - - if(!isVisible) return null; - - return ( - - setIsVisible(false) } /> - - { TABS.map(tab => ( - setCurrentTab(tab) } - > - { TAB_LABELS[tab] } - - )) } - - - { currentTab === 'color' && } - { currentTab === 'profile' && } - - - ); -}; diff --git a/src/components/loading/LoadingView.tsx b/src/components/loading/LoadingView.tsx index b947e04..a9dc9bc 100644 --- a/src/components/loading/LoadingView.tsx +++ b/src/components/loading/LoadingView.tsx @@ -2,21 +2,29 @@ import { FC } from 'react'; import { Base, Column, Text } from '../../common'; interface LoadingViewProps { - isError: boolean; - message: string; + isError?: boolean; + message?: string; } export const LoadingView: FC = props => { const { isError = false, message = '' } = props; - + return ( - - + { !isError && + } + { isError && (message && message.length) ? - { message } + + + Something went wrong while loading + + + { message } + + : The hotel is loading ... diff --git a/src/components/mod-tools/views/tickets/ModToolsMyIssuesTabView.tsx b/src/components/mod-tools/views/tickets/ModToolsMyIssuesTabView.tsx index a8de00e..9aaa441 100644 --- a/src/components/mod-tools/views/tickets/ModToolsMyIssuesTabView.tsx +++ b/src/components/mod-tools/views/tickets/ModToolsMyIssuesTabView.tsx @@ -1,5 +1,5 @@ import { IssueMessageData, ReleaseIssuesMessageComposer } from '@nitrots/nitro-renderer'; -import { FC } from 'react'; +import { FC, useRef } from 'react'; import { SendMessageComposer } from '../../../../api'; import { Button, Column, Grid } from '../../../../common'; @@ -12,6 +12,17 @@ interface ModToolsMyIssuesTabViewProps export const ModToolsMyIssuesTabView: FC = props => { const { myIssues = null, handleIssue = null } = props; + const pendingReleasesRef = useRef>(new Set()); + + const releaseIssue = (issueId: number) => + { + if(pendingReleasesRef.current.has(issueId)) return; + + pendingReleasesRef.current.add(issueId); + SendMessageComposer(new ReleaseIssuesMessageComposer([ issueId ])); + + setTimeout(() => pendingReleasesRef.current.delete(issueId), 2000); + }; return ( @@ -36,7 +47,7 @@ export const ModToolsMyIssuesTabView: FC = props =
- +
); diff --git a/src/components/mod-tools/views/tickets/ModToolsOpenIssuesTabView.tsx b/src/components/mod-tools/views/tickets/ModToolsOpenIssuesTabView.tsx index 17e4901..387580b 100644 --- a/src/components/mod-tools/views/tickets/ModToolsOpenIssuesTabView.tsx +++ b/src/components/mod-tools/views/tickets/ModToolsOpenIssuesTabView.tsx @@ -1,5 +1,5 @@ import { IssueMessageData, PickIssuesMessageComposer } from '@nitrots/nitro-renderer'; -import { FC } from 'react'; +import { FC, useRef } from 'react'; import { SendMessageComposer } from '../../../../api'; import { Button, Column, Grid } from '../../../../common'; @@ -11,6 +11,17 @@ interface ModToolsOpenIssuesTabViewProps export const ModToolsOpenIssuesTabView: FC = props => { const { openIssues = null } = props; + const pendingPicksRef = useRef>(new Set()); + + const pickIssue = (issueId: number) => + { + if(pendingPicksRef.current.has(issueId)) return; + + pendingPicksRef.current.add(issueId); + SendMessageComposer(new PickIssuesMessageComposer([ issueId ], false, 0, 'pick issue button')); + + setTimeout(() => pendingPicksRef.current.delete(issueId), 2000); + }; return ( @@ -31,7 +42,7 @@ export const ModToolsOpenIssuesTabView: FC = pro
{ issue.reportedUserName }
{ new Date(Date.now() - issue.issueAgeInMilliseconds).toLocaleTimeString() }
- +
); diff --git a/src/components/mod-tools/views/user/ModToolsUserModActionView.tsx b/src/components/mod-tools/views/user/ModToolsUserModActionView.tsx index 2dcdd3e..1bea10a 100644 --- a/src/components/mod-tools/views/user/ModToolsUserModActionView.tsx +++ b/src/components/mod-tools/views/user/ModToolsUserModActionView.tsx @@ -1,5 +1,5 @@ import { CallForHelpTopicData, DefaultSanctionMessageComposer, ModAlertMessageComposer, ModBanMessageComposer, ModKickMessageComposer, ModMessageMessageComposer, ModMuteMessageComposer, ModTradingLockMessageComposer } from '@nitrots/nitro-renderer'; -import { FC, useMemo, useState } from 'react'; +import { FC, useMemo, useRef, useState } from 'react'; import { ISelectedUser, LocalizeText, ModActionDefinition, NotificationAlertType, SendMessageComposer } from '../../../../api'; import { Button, DraggableWindowPosition, Flex, NitroCardContentView, NitroCardHeaderView, NitroCardView, Text } from '../../../../common'; import { useModTools, useNotification } from '../../../../hooks'; @@ -33,6 +33,7 @@ export const ModToolsUserModActionView: FC = pro const [ message, setMessage ] = useState(''); const { cfhCategories = null, settings = null } = useModTools(); const { simpleAlert = null } = useNotification(); + const isSendingRef = useRef(false); const topics = useMemo(() => { @@ -53,6 +54,8 @@ export const ModToolsUserModActionView: FC = pro const sendDefaultSanction = () => { + if(isSendingRef.current) return; + let errorMessage: string = null; const category = topics[selectedTopic]; @@ -63,6 +66,8 @@ export const ModToolsUserModActionView: FC = pro const messageOrDefault = (message.trim().length === 0) ? LocalizeText(`help.cfh.topic.${ category.id }`) : message; + isSendingRef.current = true; + SendMessageComposer(new DefaultSanctionMessageComposer(user.userId, selectedTopic, messageOrDefault)); onCloseClick(); @@ -70,6 +75,8 @@ export const ModToolsUserModActionView: FC = pro const sendSanction = () => { + if(isSendingRef.current) return; + let errorMessage: string = null; const category = topics[selectedTopic]; @@ -145,6 +152,8 @@ export const ModToolsUserModActionView: FC = pro } } + isSendingRef.current = true; + onCloseClick(); }; diff --git a/src/components/navigator/views/NavigatorRoomCreatorView.tsx b/src/components/navigator/views/NavigatorRoomCreatorView.tsx index 0b4adf4..9307a4b 100644 --- a/src/components/navigator/views/NavigatorRoomCreatorView.tsx +++ b/src/components/navigator/views/NavigatorRoomCreatorView.tsx @@ -6,6 +6,9 @@ import { Button, Flex, Grid, LayoutCurrencyIcon, LayoutGridItem, Text } from '.. import { useNavigator } from '../../../hooks'; import { NitroInput } from '../../../layout'; +let isCreatingRoom = false; +let createRoomTimeout: ReturnType = null; + export const NavigatorRoomCreatorView: FC<{}> = props => { const [ maxVisitorsList, setMaxVisitorsList ] = useState(null); @@ -16,6 +19,7 @@ export const NavigatorRoomCreatorView: FC<{}> = props => const [ tradesSetting, setTradesSetting ] = useState(0); const [ roomModels, setRoomModels ] = useState([]); const [ selectedModelName, setSelectedModelName ] = useState(''); + const [ isCreating, setIsCreating ] = useState(isCreatingRoom); const { categories = null } = useNavigator(); const hcDisabled = GetConfigurationValue('hc.disabled', false); @@ -31,7 +35,19 @@ export const NavigatorRoomCreatorView: FC<{}> = props => const createRoom = () => { + if(isCreatingRoom) return; + + isCreatingRoom = true; + setIsCreating(true); + SendMessageComposer(new CreateFlatMessageComposer(name, description, 'model_' + selectedModelName, Number(category), Number(visitorsCount), tradesSetting)); + + if(createRoomTimeout) clearTimeout(createRoomTimeout); + createRoomTimeout = setTimeout(() => + { + isCreatingRoom = false; + setIsCreating(false); + }, 5000); }; useEffect(() => @@ -117,7 +133,7 @@ export const NavigatorRoomCreatorView: FC<{}> = props => }
- +
); }; diff --git a/src/components/navigator/views/room-settings/NavigatorRoomSettingsRightsTabView.tsx b/src/components/navigator/views/room-settings/NavigatorRoomSettingsRightsTabView.tsx index 3f77676..6f166ae 100644 --- a/src/components/navigator/views/room-settings/NavigatorRoomSettingsRightsTabView.tsx +++ b/src/components/navigator/views/room-settings/NavigatorRoomSettingsRightsTabView.tsx @@ -1,5 +1,5 @@ import { FlatControllerAddedEvent, FlatControllerRemovedEvent, FlatControllersEvent, RemoveAllRightsMessageComposer, RoomGiveRightsComposer, RoomTakeRightsComposer, RoomUsersWithRightsComposer } from '@nitrots/nitro-renderer'; -import { FC, useEffect, useState } from 'react'; +import { FC, useEffect, useRef, useState } from 'react'; import { IRoomData, LocalizeText, SendMessageComposer } from '../../../../api'; import { Button, Column, Flex, Grid, Text, UserProfileIconView } from '../../../../common'; import { useFriends, useMessageEvent } from '../../../../hooks'; @@ -18,6 +18,17 @@ export const NavigatorRoomSettingsRightsTabView: FC>(new Map()); const { onlineFriends = [], offlineFriends = [] } = useFriends(); + const pendingActionsRef = useRef>(new Set()); + + const guardedSend = (key: string, composer: any) => + { + if(pendingActionsRef.current.has(key)) return; + + pendingActionsRef.current.add(key); + SendMessageComposer(composer); + + setTimeout(() => pendingActionsRef.current.delete(key), 2000); + }; const allFriendsRaw = [ ...onlineFriends, ...offlineFriends ]; @@ -115,7 +126,7 @@ export const NavigatorRoomSettingsRightsTabView: FC SendMessageComposer(new RoomTakeRightsComposer(id)) }> + onClick={ () => guardedSend(`take_${id}`, new RoomTakeRightsComposer(id)) }> { name }
@@ -127,7 +138,7 @@ export const NavigatorRoomSettingsRightsTabView: FC roomData && SendMessageComposer(new RemoveAllRightsMessageComposer(roomData.roomId)) }> + onClick={ () => roomData && guardedSend('removeAll', new RemoveAllRightsMessageComposer(roomData.roomId)) }> { LocalizeText('navigator.flatctrls.clear') }
@@ -154,7 +165,7 @@ export const NavigatorRoomSettingsRightsTabView: FC SendMessageComposer(new RoomGiveRightsComposer(friend.id)) }> + onClick={ () => guardedSend(`give_${friend.id}`, new RoomGiveRightsComposer(friend.id)) }> { friend.name }
diff --git a/src/components/notification-center/views/confirm-layouts/GetConfirmLayout.tsx b/src/components/notification-center/views/confirm-layouts/GetConfirmLayout.tsx index 3c5720c..fd142de 100644 --- a/src/components/notification-center/views/confirm-layouts/GetConfirmLayout.tsx +++ b/src/components/notification-center/views/confirm-layouts/GetConfirmLayout.tsx @@ -10,6 +10,6 @@ export const GetConfirmLayout = (item: NotificationConfirmItem, onClose: () => v switch(item.confirmType) { default: - return ; + return ; } }; diff --git a/src/components/reconnect/ReconnectView.tsx b/src/components/reconnect/ReconnectView.tsx new file mode 100644 index 0000000..5dc2acf --- /dev/null +++ b/src/components/reconnect/ReconnectView.tsx @@ -0,0 +1,108 @@ +import { NitroEventType, ReconnectEvent } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useState } from 'react'; +import { Base, Column, Text } from '../../common'; +import { useNitroEvent } from '../../hooks'; + +export const ReconnectView: FC<{}> = props => +{ + const [ isReconnecting, setIsReconnecting ] = useState(false); + const [ attempt, setAttempt ] = useState(0); + const [ maxAttempts, setMaxAttempts ] = useState(0); + const [ hasFailed, setHasFailed ] = useState(false); + + const onReconnecting = useCallback((event: ReconnectEvent) => + { + setIsReconnecting(true); + setHasFailed(false); + setAttempt(event.attempt); + setMaxAttempts(event.maxAttempts); + }, []); + + const onReconnected = useCallback(() => + { + setIsReconnecting(false); + setHasFailed(false); + setAttempt(0); + }, []); + + const onReconnectFailed = useCallback(() => + { + setIsReconnecting(false); + setHasFailed(true); + }, []); + + useNitroEvent(NitroEventType.SOCKET_RECONNECTING, onReconnecting); + useNitroEvent(NitroEventType.SOCKET_RECONNECTED, onReconnected); + useNitroEvent(NitroEventType.SOCKET_RECONNECT_FAILED, onReconnectFailed); + + const handleReload = useCallback(() => + { + window.location.reload(); + }, []); + + const handleGoHome = useCallback(() => + { + sessionStorage.removeItem('nitro_last_room'); + sessionStorage.removeItem('nitro_last_room_password'); + window.location.reload(); + }, []); + + if(!isReconnecting && !hasFailed) return null; + + return ( + + + { isReconnecting && ( + <> + + + Connection lost + + + Reconnecting to server... (attempt { attempt }/{ maxAttempts }) + + + + + + Please wait, your session will be restored automatically + + + ) } + + { hasFailed && ( + <> + + + Connection failed + + + Unable to reconnect to the server after multiple attempts. + + + + Reload Page + + + Go to Home + + + + ) } + + + ); +}; diff --git a/src/components/room/RoomView.tsx b/src/components/room/RoomView.tsx index afec584..e2a8764 100644 --- a/src/components/room/RoomView.tsx +++ b/src/components/room/RoomView.tsx @@ -43,10 +43,11 @@ export const RoomView: FC<{}> = (props) => { -
+
{ roomSession instanceof RoomSession && <> diff --git a/src/components/room/widgets/avatar-info/infostand/InfoStandBadgeSlotView.tsx b/src/components/room/widgets/avatar-info/infostand/InfoStandBadgeSlotView.tsx index dde184d..6a504d2 100644 --- a/src/components/room/widgets/avatar-info/infostand/InfoStandBadgeSlotView.tsx +++ b/src/components/room/widgets/avatar-info/infostand/InfoStandBadgeSlotView.tsx @@ -45,8 +45,7 @@ const BadgeMiniPicker: FC<{ return (
e.stopPropagation() }> = props canUse = true; isCrackable = true; - crackableHits = stuffData?.hits ?? 0; - crackableTarget = stuffData?.target ?? 0; + crackableHits = stuffData.hits; + crackableTarget = stuffData.target; } else if(avatarInfo.extraParam === RoomWidgetEnumItemExtradataParameter.JUKEBOX) @@ -458,7 +458,7 @@ export const InfoStandWidgetFurniView: FC = props return ( - +
@@ -527,7 +527,7 @@ export const InfoStandWidgetFurniView: FC = props { isCrackable && <>
- { LocalizeText('infostand.crackable_furni.hits_remaining', [ 'hits', 'target' ], [ (crackableHits ?? 0).toString(), (crackableTarget ?? 0).toString() ]) } + { LocalizeText('infostand.crackable_furni.hits_remaining', [ 'hits', 'target' ], [ crackableHits.toString(), crackableTarget.toString() ]) } } { avatarInfo.groupId > 0 && <> @@ -552,21 +552,7 @@ export const InfoStandWidgetFurniView: FC = props { godMode && <>
- { canSeeFurniId && -
-
- - - - ID: { avatarInfo.id } -
-
- - - - Sprite: { (() => { const ro = GetRoomEngine().getRoomObject(roomSession.roomId, avatarInfo.id, avatarInfo.isWallItem ? RoomObjectCategory.WALL : RoomObjectCategory.FLOOR); return ro?.model?.getValue(RoomObjectVariable.FURNITURE_TYPE_ID) ?? '?'; })() } -
-
} + { canSeeFurniId && ID: { avatarInfo.id } } { (!avatarInfo.isWallItem && canMove) && <> - { dropdownOpen &&
{ /* Left panel: position + rotation */ } diff --git a/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetUserView.tsx b/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetUserView.tsx index 2dac49e..1791979 100644 --- a/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetUserView.tsx +++ b/src/components/room/widgets/avatar-info/infostand/InfoStandWidgetUserView.tsx @@ -1,4 +1,4 @@ -import { CreateLinkEvent, GetSessionDataManager, RelationshipStatusInfoEvent, RelationshipStatusInfoMessageParser, RoomSessionFavoriteGroupUpdateEvent, RoomSessionUserBadgesEvent, RoomSessionUserFigureUpdateEvent, UserRelationshipsComposer } from '@nitrots/nitro-renderer'; +import { GetSessionDataManager, RelationshipStatusInfoEvent, RelationshipStatusInfoMessageParser, RoomSessionFavoriteGroupUpdateEvent, RoomSessionUserBadgesEvent, RoomSessionUserFigureUpdateEvent, UserRelationshipsComposer } from '@nitrots/nitro-renderer'; import { Dispatch, FC, FocusEvent, KeyboardEvent, SetStateAction, useCallback, useEffect, useState } from 'react'; import { FaPencilAlt, FaTimes } from 'react-icons/fa'; import { AvatarInfoUser, CloneObject, GetConfigurationValue, GetGroupInformation, GetUserProfile, LocalizeText, SendMessageComposer } from '../../../../../api'; @@ -7,6 +7,7 @@ import { useMessageEvent, useNitroEvent, useRoom } from '../../../../../hooks'; import { InfoStandBadgeSlotView } from './InfoStandBadgeSlotView'; import { InfoStandWidgetUserRelationshipsView } from './InfoStandWidgetUserRelationshipsView'; import { InfoStandWidgetUserTagsView } from './InfoStandWidgetUserTagsView'; +import { BackgroundsView } from '../../../../backgrounds/BackgroundsView'; interface InfoStandWidgetUserViewProps { avatarInfo: AvatarInfoUser; @@ -31,7 +32,7 @@ export const InfoStandWidgetUserView: FC = props = const handleProfileClick = useCallback(() => { GetUserProfile(avatarInfo.webID); }, [avatarInfo.webID]); - const handleEditClick = useCallback((event: React.MouseEvent) => { event.stopPropagation(); CreateLinkEvent('interface-settings/profile'); }, []); + const handleEditClick = useCallback((event: React.MouseEvent) => { event.stopPropagation(); setIsVisible(prev => !prev); }, []); const saveMotto = (motto: string) => { if (!isEditingMotto || motto.length > GetConfigurationValue('motto.max.length', 38) || !roomSession) return; @@ -126,7 +127,7 @@ export const InfoStandWidgetUserView: FC = props = return ( <> - +
@@ -256,6 +257,19 @@ export const InfoStandWidgetUserView: FC = props = )} + {isVisible && avatarInfo.type === AvatarInfoUser.OWN_USER && ( +
+ +
+ )} ); }; \ No newline at end of file diff --git a/src/components/room/widgets/context-menu/ContextMenuHeaderView.tsx b/src/components/room/widgets/context-menu/ContextMenuHeaderView.tsx index ff26177..a0513cf 100644 --- a/src/components/room/widgets/context-menu/ContextMenuHeaderView.tsx +++ b/src/components/room/widgets/context-menu/ContextMenuHeaderView.tsx @@ -3,21 +3,16 @@ import { Flex, FlexProps } from '../../../../common'; export const ContextMenuHeaderView: FC = props => { - const { justifyContent = 'center', alignItems = 'center', classNames = [], style = {}, ...rest } = props; + const { justifyContent = 'center', alignItems = 'center', classNames = [], ...rest } = props; const getClassNames = useMemo(() => { - const newClassNames: string[] = [ 'text-[#fff] min-w-[117px] h-[25px] max-h-[25px] text-[16px] mb-[2px]', 'p-1' ]; + const newClassNames: string[] = [ 'bg-[#3d5f6e] text-[#fff] min-w-[117px] h-[25px] max-h-[25px] text-[16px] mb-[2px]', 'p-1' ]; if(classNames.length) newClassNames.push(...classNames); return newClassNames; }, [ classNames ]); - const mergedStyle = useMemo(() => ({ - backgroundColor: 'var(--ui-ctx-header-bg, #3d5f6e)', - ...style - }), [ style ]); - - return ; + return ; }; diff --git a/src/components/room/widgets/context-menu/ContextMenuListItemView.tsx b/src/components/room/widgets/context-menu/ContextMenuListItemView.tsx index b012a93..0a1eacc 100644 --- a/src/components/room/widgets/context-menu/ContextMenuListItemView.tsx +++ b/src/components/room/widgets/context-menu/ContextMenuListItemView.tsx @@ -8,7 +8,7 @@ interface ContextMenuListItemViewProps extends FlexProps export const ContextMenuListItemView: FC = props => { - const { disabled = false, fullWidth = true, justifyContent = 'center', alignItems = 'center', classNames = [], style = {}, onClick = null, ...rest } = props; + const { disabled = false, fullWidth = true, justifyContent = 'center', alignItems = 'center', classNames = [], onClick = null, ...rest } = props; const handleClick = (event: MouseEvent) => { @@ -19,7 +19,7 @@ export const ContextMenuListItemView: FC = props = const getClassNames = useMemo(() => { - const newClassNames: string[] = [ 'relative mb-[2px] p-[3px] overflow-hidden', 'h-[24px] max-h-[24px] p-[3px] cursor-pointer' ]; + const newClassNames: string[] = [ 'relative mb-[2px] p-[3px] overflow-hidden', 'h-[24px] max-h-[24px] p-[3px] bg-[repeating-linear-gradient(#131e25,#131e25_50%,#0d171d_50%,#0d171d_100%)] cursor-pointer' ]; if(disabled) newClassNames.push('disabled'); @@ -28,10 +28,5 @@ export const ContextMenuListItemView: FC = props = return newClassNames; }, [ disabled, classNames ]); - const mergedStyle = useMemo(() => ({ - background: 'repeating-linear-gradient(var(--ui-ctx-item-bg1, #131e25), var(--ui-ctx-item-bg1, #131e25) 50%, var(--ui-ctx-item-bg2, #0d171d) 50%, var(--ui-ctx-item-bg2, #0d171d) 100%)', - ...style - }), [ style ]); - - return ; + return ; }; diff --git a/src/components/room/widgets/context-menu/ContextMenuView.tsx b/src/components/room/widgets/context-menu/ContextMenuView.tsx index b92dc89..1ca3e83 100644 --- a/src/components/room/widgets/context-menu/ContextMenuView.tsx +++ b/src/components/room/widgets/context-menu/ContextMenuView.tsx @@ -76,6 +76,7 @@ export const ContextMenuView: FC = ({ const getClassNames = useMemo(() => { const classes = [ 'p-[2px]!', + 'bg-[#1c323f]', 'border-2', 'border-[solid]', 'border-[rgba(255,255,255,.5)]', @@ -97,7 +98,6 @@ export const ContextMenuView: FC = ({ top: pos.y ?? 0, transition: isFading ? 'opacity 75ms linear' : undefined, opacity, - backgroundColor: 'var(--ui-ctx-bg, #1c323f)', ...style, }), [pos, opacity, isFading, style] diff --git a/src/components/toolbar/ToolbarView.tsx b/src/components/toolbar/ToolbarView.tsx index 62503bb..7d6743a 100644 --- a/src/components/toolbar/ToolbarView.tsx +++ b/src/components/toolbar/ToolbarView.tsx @@ -69,38 +69,38 @@ export const ToolbarView: FC<{ isInRoom: boolean }> = props => )} - - - - { - setMeExpanded(!isMeExpanded); - event.stopPropagation(); - } }> - - { (getTotalUnseen > 0) && - } + + + + + { + setMeExpanded(!isMeExpanded); + event.stopPropagation(); + } }> + + { (getTotalUnseen > 0) && + } + + { isInRoom && + VisitDesktop() } /> } + { !isInRoom && + CreateLinkEvent('navigator/goto/home') } /> } + CreateLinkEvent('navigator/toggle') } /> + { GetConfigurationValue('game.center.enabled') && + CreateLinkEvent('games/toggle') } /> } + CreateLinkEvent('catalog/toggle') } /> + CreateLinkEvent('inventory/toggle') }> + { (getFullCount > 0) && + } + + { isInRoom && + CreateLinkEvent('camera/toggle') } /> } + { isMod && + CreateLinkEvent('mod-tools/toggle') } /> } - { isInRoom && - VisitDesktop() } /> } - { !isInRoom && - CreateLinkEvent('navigator/goto/home') } /> } - CreateLinkEvent('navigator/toggle') } /> - { GetConfigurationValue('game.center.enabled') && - CreateLinkEvent('games/toggle') } /> } - CreateLinkEvent('catalog/toggle') } /> - CreateLinkEvent('inventory/toggle') }> - { (getFullCount > 0) && - } - - { isInRoom && - CreateLinkEvent('camera/toggle') } /> } - { isMod && - CreateLinkEvent('mod-tools/toggle') } /> } - { isMod && - CreateLinkEvent('furni-editor/toggle') } /> } + - - + CreateLinkEvent('friends/toggle') }> { (requests.length > 0) && diff --git a/src/components/wired-tools/WiredCreatorToolsView.tsx b/src/components/wired-tools/WiredCreatorToolsView.tsx new file mode 100644 index 0000000..7d5d4ed --- /dev/null +++ b/src/components/wired-tools/WiredCreatorToolsView.tsx @@ -0,0 +1,176 @@ +import { AddLinkEventTracker, ILinkEventTracker, RemoveLinkEventTracker } from '@nitrots/nitro-renderer'; +import { FC, useEffect, useMemo, useState } from 'react'; +import { Button, DraggableWindowPosition, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView, Text } from '../../common'; + +type WiredToolsTab = 'monitor' | 'variables' | 'inspection' | 'chests' | 'settings'; + +interface MonitorStat +{ + label: string; + value: string; +} + +interface MonitorLog +{ + type: string; + category: string; + amount: string; + latest: string; +} + +const TABS: Array<{ key: WiredToolsTab; label: string; }> = [ + { key: 'monitor', label: 'Monitor' }, + { key: 'variables', label: 'Variables' }, + { key: 'inspection', label: 'Inspection' }, + { key: 'chests', label: 'Chests' }, + { key: 'settings', label: 'Settings' } +]; + +const MONITOR_STATS: MonitorStat[] = [ + { label: 'Wired usage', value: '0/10000' }, + { label: 'Is heavy', value: 'No' }, + { label: 'Floor furni', value: '0/4000' }, + { label: 'Wall furni', value: '0/4000' }, + { label: 'Permanent furni vars', value: '0/60' } +]; + +const MONITOR_LOGS: MonitorLog[] = [ + { type: 'EXECUTION_CAP', category: 'ERROR', amount: '0', latest: '/' }, + { type: 'DELAYED_EVENTS_CAP', category: 'ERROR', amount: '0', latest: '/' }, + { type: 'EXECUTOR_OVERLOAD', category: 'ERROR', amount: '0', latest: '/' }, + { type: 'MARKED_AS_HEAVY', category: 'WARNING', amount: '0', latest: '/' }, + { type: 'KILLED', category: 'ERROR', amount: '0', latest: '/' }, + { type: 'RECURSION_TIMEOUT', category: 'ERROR', amount: '0', latest: '/' } +]; + +export const WiredCreatorToolsView: FC<{}> = props => +{ + const [ isVisible, setIsVisible ] = useState(false); + const [ activeTab, setActiveTab ] = useState('monitor'); + + useEffect(() => + { + const linkTracker: ILinkEventTracker = { + linkReceived: (url: string) => + { + const parts = url.split('/'); + + if(parts.length < 2) return; + + switch(parts[1]) + { + case 'show': + setIsVisible(true); + return; + case 'hide': + setIsVisible(false); + return; + case 'toggle': + setIsVisible(prevValue => !prevValue); + return; + case 'tab': + if(parts.length > 2) + { + const tab = parts[2] as WiredToolsTab; + + if(TABS.some(entry => entry.key === tab)) setActiveTab(tab); + } + setIsVisible(true); + return; + } + }, + eventUrlPrefix: 'wired-tools/' + }; + + AddLinkEventTracker(linkTracker); + + return () => RemoveLinkEventTracker(linkTracker); + }, []); + + const currentTabLabel = useMemo(() => TABS.find(tab => tab.key === activeTab)?.label ?? 'Monitor', [ activeTab ]); + + if(!isVisible) return null; + + return ( + + setIsVisible(false) } /> + + { TABS.map(tab => ( + setActiveTab(tab.key) }> + { tab.label } + + )) } + + +
+
+ { currentTabLabel } +
+ { (activeTab === 'monitor') && +
+
+ This is the initial shell for the Wired Creator Tools. We can now build the real functionality tab by tab. +
+
+
+ Statistics: + { MONITOR_STATS.map(stat => ( +
+ { stat.label }: + { stat.value } +
+ )) } +
+
+
+ Monitor Preview +
+ Live statistics, executor health and diagnostics can be connected here next. +
+
+
+
+
+ Logs: +
+ + + + + + + + + + + { MONITOR_LOGS.map((log, index) => ( + + + + + + + )) } + +
TypeCategoryAmountLatest occurrence
{ log.type }{ log.category }{ log.amount }{ log.latest }
+
+
+ + +
+
+
} + { (activeTab !== 'monitor') && +
+
+ { currentTabLabel } +
+ This tab is now ready to be wired into the new `:wired` tools flow. +
+
+
} +
+
+
+ ); +}; diff --git a/src/components/wired/views/WiredBaseView.tsx b/src/components/wired/views/WiredBaseView.tsx index 4c4f0db..f9477af 100644 --- a/src/components/wired/views/WiredBaseView.tsx +++ b/src/components/wired/views/WiredBaseView.tsx @@ -24,7 +24,11 @@ export const WiredBaseView: FC> = props => const [ needsSave, setNeedsSave ] = useState(false); const { trigger = null, setTrigger = null, setIntParams = null, setStringParam = null, setFurniIds = null, setAllowsFurni = null, saveWired = null } = useWired(); - const onClose = () => setTrigger(null); + const onClose = () => + { + WiredSelectionVisualizer.clearAllSelectionShaders(); + setTrigger(null); + }; const onSave = () => { @@ -48,6 +52,8 @@ export const WiredBaseView: FC> = props => { if(!trigger) return; + WiredSelectionVisualizer.clearAllSelectionShaders(); + const spriteId = (trigger.spriteId || -1); const furniData = GetSessionDataManager().getFloorItemData(spriteId); diff --git a/src/components/wired/views/WiredSourcesSelector.tsx b/src/components/wired/views/WiredSourcesSelector.tsx index db072a6..2d5ebc1 100644 --- a/src/components/wired/views/WiredSourcesSelector.tsx +++ b/src/components/wired/views/WiredSourcesSelector.tsx @@ -16,45 +16,66 @@ export const USER_SOURCES = [ { value: 201, label: 'wiredfurni.params.sources.users.201' } ]; +export interface WiredSourceOption +{ + value: number; + label: string; +} + interface WiredSourcesSelectorProps { showFurni?: boolean; showUsers?: boolean; furniSource?: number; userSource?: number; + furniTitle?: string; + usersTitle?: string; + furniSources?: WiredSourceOption[]; + userSources?: WiredSourceOption[]; onChangeFurni?: (source: number) => void; onChangeUsers?: (source: number) => void; } export const WiredSourcesSelector: FC = props => { - const { showFurni = false, showUsers = false, furniSource = 0, userSource = 0, onChangeFurni = null, onChangeUsers = null } = props; + const { + showFurni = false, + showUsers = false, + furniSource = 0, + userSource = 0, + furniTitle = 'wiredfurni.params.sources.furni.title', + usersTitle = 'wiredfurni.params.sources.users.title', + furniSources = FURNI_SOURCES, + userSources = USER_SOURCES, + onChangeFurni = null, + onChangeUsers = null + } = props; - const furniIndex = Math.max(0, FURNI_SOURCES.findIndex(s => s.value === furniSource)); - const userIndex = Math.max(0, USER_SOURCES.findIndex(s => s.value === userSource)); + const furniIndex = Math.max(0, furniSources.findIndex(s => s.value === furniSource)); + const userIndex = Math.max(0, userSources.findIndex(s => s.value === userSource)); const prevFurni = () => { - const next = (furniIndex - 1 + FURNI_SOURCES.length) % FURNI_SOURCES.length; - onChangeFurni && onChangeFurni(FURNI_SOURCES[next].value); + const next = (furniIndex - 1 + furniSources.length) % furniSources.length; + onChangeFurni && onChangeFurni(furniSources[next].value); }; const nextFurni = () => { - const next = (furniIndex + 1) % FURNI_SOURCES.length; - onChangeFurni && onChangeFurni(FURNI_SOURCES[next].value); + const next = (furniIndex + 1) % furniSources.length; + onChangeFurni && onChangeFurni(furniSources[next].value); }; const prevUsers = () => { - const next = (userIndex - 1 + USER_SOURCES.length) % USER_SOURCES.length; - onChangeUsers && onChangeUsers(USER_SOURCES[next].value); + const next = (userIndex - 1 + userSources.length) % userSources.length; + onChangeUsers && onChangeUsers(userSources[next].value); }; const nextUsers = () => { - const next = (userIndex + 1) % USER_SOURCES.length; - onChangeUsers && onChangeUsers(USER_SOURCES[next].value); + const next = (userIndex + 1) % userSources.length; + onChangeUsers && onChangeUsers(userSources[next].value); }; if(!showFurni && !showUsers) return null; @@ -63,11 +84,11 @@ export const WiredSourcesSelector: FC = props =>
{ showFurni && <> - { LocalizeText('wiredfurni.params.sources.furni.title') } + { LocalizeText(furniTitle) }
- { LocalizeText(FURNI_SOURCES[furniIndex].label) } + { LocalizeText(furniSources[furniIndex].label) }
@@ -77,11 +98,11 @@ export const WiredSourcesSelector: FC = props => { showUsers && <> - { LocalizeText('wiredfurni.params.sources.users.title') } + { LocalizeText(usersTitle) }
- { LocalizeText(USER_SOURCES[userIndex].label) } + { LocalizeText(userSources[userIndex].label) }
@@ -89,4 +110,3 @@ export const WiredSourcesSelector: FC = props =>
); }; - diff --git a/src/components/wired/views/actions/WiredActionFreezeView.tsx b/src/components/wired/views/actions/WiredActionFreezeView.tsx new file mode 100644 index 0000000..716acaa --- /dev/null +++ b/src/components/wired/views/actions/WiredActionFreezeView.tsx @@ -0,0 +1,54 @@ +import { FC, useEffect, useState } from 'react'; +import { LocalizeText, WiredFurniType } from '../../../../api'; +import { Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredActionBaseView } from './WiredActionBaseView'; +import { WiredSourcesSelector } from '../WiredSourcesSelector'; + +const EFFECT_OPTIONS = [ + { value: 218, label: 'fx_218' }, + { value: 12, label: 'fx_12' }, + { value: 11, label: 'fx_11' }, + { value: 53, label: 'fx_53' }, + { value: 163, label: 'fx_163' } +]; + +export const WiredActionFreezeView: FC<{}> = () => +{ + const [ effectId, setEffectId ] = useState(218); + const [ cancelOnTeleport, setCancelOnTeleport ] = useState(false); + const [ userSource, setUserSource ] = useState(0); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ + effectId, + cancelOnTeleport ? 1 : 0, + userSource + ]); + + useEffect(() => + { + setEffectId((trigger?.intData?.length > 0) ? trigger.intData[0] : 218); + setCancelOnTeleport((trigger?.intData?.length > 1) ? (trigger.intData[1] === 1) : false); + setUserSource((trigger?.intData?.length > 2) ? trigger.intData[2] : 0); + }, [ trigger ]); + + return ( + }> +
+ Effect + +
+
+ setCancelOnTeleport(event.target.checked) } /> + { LocalizeText('wiredfurni.params.freeze.cancel_on_teleport') } +
+
+ ); +}; diff --git a/src/components/wired/views/actions/WiredActionFurniToFurniView.tsx b/src/components/wired/views/actions/WiredActionFurniToFurniView.tsx new file mode 100644 index 0000000..5f7cefc --- /dev/null +++ b/src/components/wired/views/actions/WiredActionFurniToFurniView.tsx @@ -0,0 +1,222 @@ +import { FC, useCallback, useEffect, useRef, useState } from 'react'; +import { LocalizeText, WiredFurniType, WiredSelectionVisualizer } from '../../../../api'; +import { Button, Text } from '../../../../common'; +import { useWired } from '../../../../hooks'; +import { WiredSourcesSelector, FURNI_SOURCES, WiredSourceOption } from '../WiredSourcesSelector'; +import { WiredActionBaseView } from './WiredActionBaseView'; + +const SOURCE_TRIGGER = 0; +const SOURCE_SELECTED = 100; +const SOURCE_SECONDARY_SELECTED = 101; +const FURNI_DELIMITER = ';'; + +const TARGET_FURNI_SOURCES: WiredSourceOption[] = [ + { value: 0, label: 'wiredfurni.params.sources.furni.0' }, + { value: SOURCE_SECONDARY_SELECTED, label: 'wiredfurni.params.sources.furni.101' }, + { value: 200, label: 'wiredfurni.params.sources.furni.200' }, + { value: 201, label: 'wiredfurni.params.sources.furni.201' } +]; + +type SelectionMode = 'move' | 'target'; + +const parseIds = (data: string): number[] => +{ + if(!data || !data.length) return []; + + const ids = new Set(); + + for(const part of data.split(/[;,\t]/)) + { + const trimmed = part.trim(); + if(!trimmed.length) continue; + + const value = parseInt(trimmed, 10); + if(!isNaN(value) && value > 0) ids.add(value); + } + + return Array.from(ids); +}; + +const serializeIds = (ids: number[]): string => +{ + if(!ids || !ids.length) return ''; + + return ids.filter(id => (id > 0)).join(FURNI_DELIMITER); +}; + +export const WiredActionFurniToFurniView: FC<{}> = () => +{ + const [ moveSource, setMoveSource ] = useState(SOURCE_TRIGGER); + const [ targetSource, setTargetSource ] = useState(SOURCE_TRIGGER); + const [ moveFurniIds, setMoveFurniIds ] = useState([]); + const [ targetFurniIds, setTargetFurniIds ] = useState([]); + const [ selectionMode, setSelectionMode ] = useState('move'); + + const highlightedIds = useRef([]); + + const { trigger = null, furniIds = [], setFurniIds, setIntParams, setStringParam, setAllowsFurni } = useWired(); + + const syncHighlights = useCallback((nextMoveIds: number[], nextTargetIds: number[]) => + { + if(highlightedIds.current.length) + { + WiredSelectionVisualizer.clearSelectionShaderFromFurni(highlightedIds.current); + WiredSelectionVisualizer.clearSecondarySelectionShaderFromFurni(highlightedIds.current); + } + + const targetSet = new Set(nextTargetIds); + const moveOnlyIds = nextMoveIds.filter(id => !targetSet.has(id)); + + if(moveOnlyIds.length) WiredSelectionVisualizer.applySelectionShaderToFurni(moveOnlyIds); + if(nextTargetIds.length) WiredSelectionVisualizer.applySecondarySelectionShaderToFurni(nextTargetIds); + + highlightedIds.current = Array.from(new Set([ ...nextMoveIds, ...nextTargetIds ])); + }, []); + + const switchSelection = useCallback((mode: SelectionMode) => + { + const canEditMove = (moveSource === SOURCE_SELECTED); + const canEditTarget = (targetSource === SOURCE_SECONDARY_SELECTED); + + if(mode === 'move' && !canEditMove) return; + if(mode === 'target' && !canEditTarget) return; + + setSelectionMode(mode); + setFurniIds([ ...(mode === 'move' ? moveFurniIds : targetFurniIds) ]); + }, [ moveSource, targetSource, moveFurniIds, targetFurniIds, setFurniIds ]); + + useEffect(() => + { + if(!trigger) return; + + const nextMoveIds = trigger.selectedItems ?? []; + const nextTargetIds = parseIds(trigger.stringData); + const nextMoveSource = (trigger.intData.length >= 1) + ? trigger.intData[0] + : (nextMoveIds.length ? SOURCE_SELECTED : SOURCE_TRIGGER); + const nextTargetSourceRaw = (trigger.intData.length >= 2) + ? trigger.intData[1] + : (nextTargetIds.length ? SOURCE_SECONDARY_SELECTED : SOURCE_TRIGGER); + const nextTargetSource = (nextTargetSourceRaw === SOURCE_SELECTED) ? SOURCE_SECONDARY_SELECTED : nextTargetSourceRaw; + + setMoveSource(nextMoveSource); + setTargetSource(nextTargetSource); + setMoveFurniIds(nextMoveIds); + setTargetFurniIds(nextTargetIds); + setSelectionMode('move'); + setFurniIds([ ...nextMoveIds ]); + }, [ trigger, setFurniIds ]); + + useEffect(() => + { + if(selectionMode === 'move') setMoveFurniIds(furniIds); + else setTargetFurniIds(furniIds); + }, [ furniIds, selectionMode ]); + + useEffect(() => + { + syncHighlights(moveFurniIds, targetFurniIds); + }, [ moveFurniIds, targetFurniIds, syncHighlights ]); + + useEffect(() => + { + const canEditMove = (moveSource === SOURCE_SELECTED); + const canEditTarget = (targetSource === SOURCE_SECONDARY_SELECTED); + + if(selectionMode === 'move' && !canEditMove && canEditTarget) + { + switchSelection('target'); + return; + } + + if(selectionMode === 'target' && !canEditTarget && canEditMove) + { + switchSelection('move'); + return; + } + + const canEditCurrent = ((selectionMode === 'move') ? canEditMove : canEditTarget); + setAllowsFurni(canEditCurrent ? WiredFurniType.STUFF_SELECTION_OPTION_BY_ID : WiredFurniType.STUFF_SELECTION_OPTION_NONE); + }, [ selectionMode, moveSource, targetSource, switchSelection, setAllowsFurni ]); + + useEffect(() => + { + return () => + { + if(!highlightedIds.current.length) return; + + WiredSelectionVisualizer.clearSelectionShaderFromFurni(highlightedIds.current); + WiredSelectionVisualizer.clearSecondarySelectionShaderFromFurni(highlightedIds.current); + highlightedIds.current = []; + }; + }, []); + + const save = useCallback(() => + { + if(selectionMode === 'target') + { + setSelectionMode('move'); + setFurniIds([ ...moveFurniIds ]); + } + + setIntParams([ + moveSource, + targetSource + ]); + + setStringParam(serializeIds(targetFurniIds)); + }, [ selectionMode, moveFurniIds, moveSource, targetSource, targetFurniIds, setFurniIds, setIntParams, setStringParam ]); + + const selectionLimit = trigger?.maximumItemSelectionCount ?? 0; + + return ( + + +
+ setTargetSource((value === SOURCE_SELECTED) ? SOURCE_SECONDARY_SELECTED : value) } /> +
+ }> +
+
+ { LocalizeText('wiredfurni.params.sources.furni.title.mv.0') } +
+ + { selectionLimit ? `${ moveFurniIds.length }/${ selectionLimit }` : moveFurniIds.length } +
+
+
+ { LocalizeText('wiredfurni.params.sources.furni.title.mv.1') } +
+ + { selectionLimit ? `${ targetFurniIds.length }/${ selectionLimit }` : targetFurniIds.length } +
+
+
+ + ); +}; diff --git a/src/components/wired/views/actions/WiredActionGiveRewardView.tsx b/src/components/wired/views/actions/WiredActionGiveRewardView.tsx index 91c1468..3fb239a 100644 --- a/src/components/wired/views/actions/WiredActionGiveRewardView.tsx +++ b/src/components/wired/views/actions/WiredActionGiveRewardView.tsx @@ -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([]); const { trigger = null, setIntParams = null, setStringParam = null } = useWired(); const [ userSource, setUserSource ] = useState(() => { @@ -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 =>
+
+ Type + Amount / Value + { uniqueRewards ? 'Mode' : 'Chance %' } + { hasCustomCurrencyReward ? 'Currency Type' : 'Extra / Info' } + Action +
{ rewards && rewards.map((reward, index) => { + const rewardTypeOptions = (reward.rewardType === 'respect') + ? REWARD_TYPES + : SELECTABLE_REWARD_TYPES; + return ( -
-
- updateReward(index, e.target.checked, reward.itemCode, reward.probability) } /> - Badge? +
+ + updateReward(index, prevValue => ({ ...prevValue, rewardValue: event.target.value })) } /> + { uniqueRewards + ?
+ Unique +
+ : updateReward(index, prevValue => ({ ...prevValue, probability: Number(event.target.value) })) } /> } + { (reward.rewardType === 'points') + ? + updateReward(index, prevValue => ({ ...prevValue, pointsType: Number(event.target.value) })) } /> + :
+ { getExtraFieldLabel(reward.rewardType) } +
} +
+ { (index > 0) && + }
- updateReward(index, reward.isBadge, e.target.value, reward.probability) } /> - updateReward(index, reward.isBadge, reward.itemCode, Number(e.target.value)) } /> - { (index > 0) && - }
); }) }
+ + Extra Currency uses Amount as the quantity and Currency Type as the purse type id. Example: amount 200 + type 105. + ); }; diff --git a/src/components/wired/views/actions/WiredActionLayoutView.tsx b/src/components/wired/views/actions/WiredActionLayoutView.tsx index 23c16d1..551da1a 100644 --- a/src/components/wired/views/actions/WiredActionLayoutView.tsx +++ b/src/components/wired/views/actions/WiredActionLayoutView.tsx @@ -1,5 +1,7 @@ import { WiredActionLayoutCode } from '../../../../api'; import { WiredActionBotChangeFigureView } from './WiredActionBotChangeFigureView'; +import { WiredActionFreezeView } from './WiredActionFreezeView'; +import { WiredActionFurniToFurniView } from './WiredActionFurniToFurniView'; import { WiredActionSendSignalView } from './WiredActionSendSignalView'; import { WiredActionFurniAreaView } from '../selectors/WiredActionFurniAreaView'; import { WiredSelectorFurniNeighborhoodView } from '../selectors/WiredSelectorFurniNeighborhoodView'; @@ -26,10 +28,12 @@ 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'; import { WiredActionToggleFurniStateView } from './WiredActionToggleFurniStateView'; +import { WiredActionUnfreezeView } from './WiredActionUnfreezeView'; export const WiredActionLayoutView = (code: number) => { @@ -57,6 +61,12 @@ export const WiredActionLayoutView = (code: number) => return ; case WiredActionLayoutCode.FLEE: return ; + case WiredActionLayoutCode.FREEZE: + return ; + case WiredActionLayoutCode.FURNI_TO_USER: + return ; + case WiredActionLayoutCode.FURNI_TO_FURNI: + return ; case WiredActionLayoutCode.GIVE_REWARD: return ; case WiredActionLayoutCode.GIVE_SCORE: @@ -77,6 +87,8 @@ export const WiredActionLayoutView = (code: number) => return ; case WiredActionLayoutCode.MUTE_USER: return ; + case WiredActionLayoutCode.RELATIVE_MOVE: + return ; case WiredActionLayoutCode.RESET: return ; case WiredActionLayoutCode.SET_FURNI_STATE: @@ -85,6 +97,10 @@ export const WiredActionLayoutView = (code: number) => return ; case WiredActionLayoutCode.TOGGLE_FURNI_STATE: return ; + case WiredActionLayoutCode.UNFREEZE: + return ; + case WiredActionLayoutCode.USER_TO_FURNI: + return ; case WiredActionLayoutCode.FURNI_AREA_SELECTOR: return ; case WiredActionLayoutCode.FURNI_NEIGHBORHOOD_SELECTOR: diff --git a/src/components/wired/views/actions/WiredActionRelativeMoveView.tsx b/src/components/wired/views/actions/WiredActionRelativeMoveView.tsx new file mode 100644 index 0000000..c6f33a1 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionRelativeMoveView.tsx @@ -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: }, + { value: 1, icon: } +]; + +const VERTICAL_OPTIONS = [ + { value: 0, icon: }, + { value: 1, icon: } +]; + +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(() => + { + 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 ( + }> +
+ { LocalizeText('wiredfurni.params.movement.horizontal.selection') } +
+ { HORIZONTAL_OPTIONS.map(option => + { + return ( + + ); + }) } +
+ { LocalizeText('wiredfurni.params.movement.horizontal.distance', [ 'distance' ], [ horizontalDistance.toString() ]) } + setHorizontalDistance(value as number) } /> +
+
+ { LocalizeText('wiredfurni.params.movement.vertical.selection') } +
+ { VERTICAL_OPTIONS.map(option => + { + return ( + + ); + }) } +
+ { LocalizeText('wiredfurni.params.movement.vertical.distance', [ 'distance' ], [ verticalDistance.toString() ]) } + setVerticalDistance(value as number) } /> +
+
+ ); +}; diff --git a/src/components/wired/views/actions/WiredActionSetAltitudeView.tsx b/src/components/wired/views/actions/WiredActionSetAltitudeView.tsx new file mode 100644 index 0000000..3e4017b --- /dev/null +++ b/src/components/wired/views/actions/WiredActionSetAltitudeView.tsx @@ -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(() => + { + 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 ( + }> +
+ { OPERATOR_OPTIONS.map(option => + { + return ( +
+ setOperator(option.value) } /> + { LocalizeText(option.label) } +
+ ); + }) } +
+
+ { LocalizeText('wiredfurni.params.setaltitude') } + setAltitudeInput(formatAltitude(altitude)) } + onChange={ event => updateAltitudeInput(event.target.value) } /> +
+
+ updateAltitude(event as number) } /> + { normalizedAltitudeText } +
+
+ ); +}; diff --git a/src/components/wired/views/actions/WiredActionUnfreezeView.tsx b/src/components/wired/views/actions/WiredActionUnfreezeView.tsx new file mode 100644 index 0000000..98b31a6 --- /dev/null +++ b/src/components/wired/views/actions/WiredActionUnfreezeView.tsx @@ -0,0 +1,26 @@ +import { FC, useEffect, useState } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { useWired } from '../../../../hooks'; +import { WiredActionBaseView } from './WiredActionBaseView'; +import { WiredSourcesSelector } from '../WiredSourcesSelector'; + +export const WiredActionUnfreezeView: FC<{}> = () => +{ + const [ userSource, setUserSource ] = useState(0); + const { trigger = null, setIntParams = null } = useWired(); + + const save = () => setIntParams([ userSource ]); + + useEffect(() => + { + setUserSource((trigger?.intData?.length > 0) ? trigger.intData[0] : 0); + }, [ trigger ]); + + return ( + } /> + ); +}; 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 a1a88c2..888df16 100644 --- a/src/components/wired/views/conditions/WiredConditionLayoutView.tsx +++ b/src/components/wired/views/conditions/WiredConditionLayoutView.tsx @@ -5,7 +5,11 @@ import { WiredConditionActorIsOnFurniView } from './WiredConditionActorIsOnFurni import { WiredConditionActorIsTeamMemberView } from './WiredConditionActorIsTeamMemberView'; import { WiredConditionActorIsWearingBadgeView } from './WiredConditionActorIsWearingBadgeView'; 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'; @@ -13,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) => @@ -20,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 ; @@ -38,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 ; @@ -58,6 +74,18 @@ export const WiredConditionLayoutView = (code: number) => case WiredConditionlayout.USER_COUNT_IN: case WiredConditionlayout.NOT_USER_COUNT_IN: 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 && + } +
} + + ); +}; diff --git a/src/components/wired/views/triggers/WiredTriggerAvatarLeaveRoomView.tsx b/src/components/wired/views/triggers/WiredTriggerAvatarLeaveRoomView.tsx new file mode 100644 index 0000000..36a773e --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerAvatarLeaveRoomView.tsx @@ -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 ( + +
+ { LocalizeText('wiredfurni.params.picktriggerer') } +
+ setAvatarMode(0) } /> + { LocalizeText('wiredfurni.params.anyavatar') } +
+
+ setAvatarMode(1) } /> + { LocalizeText('wiredfurni.params.certainavatar') } +
+ { (avatarMode === 1) && + setUsername(event.target.value) } /> } +
+
+ ); +}; diff --git a/src/components/wired/views/triggers/WiredTriggerClickFurniView.tsx b/src/components/wired/views/triggers/WiredTriggerClickFurniView.tsx new file mode 100644 index 0000000..db8fbea --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerClickFurniView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredTriggerBaseView } from './WiredTriggerBaseView'; + +export const WiredTriggerClickFurniView: FC<{}> = () => +{ + return ; +}; diff --git a/src/components/wired/views/triggers/WiredTriggerClickTileView.tsx b/src/components/wired/views/triggers/WiredTriggerClickTileView.tsx new file mode 100644 index 0000000..5a1fdc7 --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerClickTileView.tsx @@ -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 ; +}; diff --git a/src/components/wired/views/triggers/WiredTriggerClickUserView.tsx b/src/components/wired/views/triggers/WiredTriggerClickUserView.tsx new file mode 100644 index 0000000..f3f2222 --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerClickUserView.tsx @@ -0,0 +1,8 @@ +import { FC } from 'react'; +import { WiredFurniType } from '../../../../api'; +import { WiredTriggerBaseView } from './WiredTriggerBaseView'; + +export const WiredTriggerClickUserView: FC<{}> = () => +{ + return ; +}; diff --git a/src/components/wired/views/triggers/WiredTriggerExecutePeriodicallyShortView.tsx b/src/components/wired/views/triggers/WiredTriggerExecutePeriodicallyShortView.tsx new file mode 100644 index 0000000..642b5cd --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerExecutePeriodicallyShortView.tsx @@ -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 ( + +
+ { LocalizeText('wiredfurni.params.settime', [ 'seconds' ], [ ((time * 50) / 1000).toFixed(2) ]) } + { `${ time * 50 } ms` } + setTime(event) } /> +
+
+ ); +}; diff --git a/src/components/wired/views/triggers/WiredTriggerLayoutView.tsx b/src/components/wired/views/triggers/WiredTriggerLayoutView.tsx index 229464f..3b152fb 100644 --- a/src/components/wired/views/triggers/WiredTriggerLayoutView.tsx +++ b/src/components/wired/views/triggers/WiredTriggerLayoutView.tsx @@ -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 ; + case WiredTriggerLayout.AVATAR_LEAVES_ROOM: + return ; case WiredTriggerLayout.AVATAR_SAYS_SOMETHING: return ; case WiredTriggerLayout.AVATAR_WALKS_OFF_FURNI: @@ -31,12 +39,22 @@ export const WiredTriggerLayoutView = (code: number) => return ; case WiredTriggerLayout.BOT_REACHED_STUFF: return ; + case WiredTriggerLayout.CLICK_FURNI: + return ; + case WiredTriggerLayout.CLICK_TILE: + return ; + case WiredTriggerLayout.CLICK_USER: + return ; + case WiredTriggerLayout.USER_PERFORMS_ACTION: + return ; case WiredTriggerLayout.COLLISION: return ; case WiredTriggerLayout.EXECUTE_ONCE: return ; case WiredTriggerLayout.EXECUTE_PERIODICALLY: return ; + case WiredTriggerLayout.EXECUTE_PERIODICALLY_SHORT: + return ; case WiredTriggerLayout.EXECUTE_PERIODICALLY_LONG: return ; case WiredTriggerLayout.GAME_ENDS: diff --git a/src/components/wired/views/triggers/WiredTriggerToggleFurniView.tsx b/src/components/wired/views/triggers/WiredTriggerToggleFurniView.tsx index 4748481..01cb8a4 100644 --- a/src/components/wired/views/triggers/WiredTriggerToggleFurniView.tsx +++ b/src/components/wired/views/triggers/WiredTriggerToggleFurniView.tsx @@ -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 ; + 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 ( + +
+ { LocalizeText('wiredfurni.params.condition.state') } +
+ setTriggerMode(1) } /> + { LocalizeText('wiredfurni.params.state_trigger.1') } +
+
+ setTriggerMode(0) } /> + { LocalizeText('wiredfurni.params.state_trigger.0') } +
+
+
+ ); }; diff --git a/src/components/wired/views/triggers/WiredTriggerUserPerformsActionView.tsx b/src/components/wired/views/triggers/WiredTriggerUserPerformsActionView.tsx new file mode 100644 index 0000000..5e68651 --- /dev/null +++ b/src/components/wired/views/triggers/WiredTriggerUserPerformsActionView.tsx @@ -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 ( + +
+ 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 && + } +
} +
+ ); +}; diff --git a/src/css/common/Buttons.css b/src/css/common/Buttons.css index 21d7f1f..106a3cc 100644 --- a/src/css/common/Buttons.css +++ b/src/css/common/Buttons.css @@ -24,8 +24,8 @@ input[type=number] { .btn-primary { color: #fff; - background-color: var(--ui-btn-primary-bg, #3c6d82); - border: 2px solid var(--ui-btn-primary-border, #1a617f); + background-color: #3c6d82; + border: 2px solid #1a617f; padding: 0.25rem 0.5rem; font-size: .7875rem; border-radius: 0.5rem; @@ -33,7 +33,7 @@ input[type=number] { } .btn-primary:hover { - border: 2px solid var(--ui-btn-primary-border, #1a617f); + border: 2px solid #1a617f; box-shadow: none!important; } @@ -81,16 +81,16 @@ input[type=number] { .btn-dark { color: #fff; - background-color: var(--ui-dark-bg, #212131); - border: 2px solid var(--ui-dark-border, #1c1c2a); + background-color: #212131; + border: 2px solid #1c1c2a; box-shadow: none!important; border-radius: 8px; padding: 4px 11px 4px 11px; } .btn-dark:hover{ - background-color: var(--ui-dark-bg, #212131); - border: 2px solid var(--ui-dark-border, #1c1c2a); + background-color: #212131; + border: 2px solid #1c1c2a; box-shadow: none!important; border-radius: 8px; padding: 4px 11px 4px 11px; diff --git a/src/css/index.css b/src/css/index.css index 36d3b21..806e0e5 100644 --- a/src/css/index.css +++ b/src/css/index.css @@ -13,11 +13,16 @@ body { width: 100%; height: 100%; overflow: hidden; + background-color: #000; -webkit-user-select: none; user-select: none; scrollbar-width: thin; } +.image-rendering-pixelated { + image-rendering: pixelated; +} + *, *:focus, *:hover { diff --git a/src/css/notification/NotificationCenterView.css b/src/css/notification/NotificationCenterView.css index af7ba41..45e3e4b 100644 --- a/src/css/notification/NotificationCenterView.css +++ b/src/css/notification/NotificationCenterView.css @@ -59,8 +59,8 @@ .alertView_nitro-coolui-logo { width: 150px; - height: 78px; + height: 73px; position: relative; - background-image: url("@/assets/images/notifications/coolui.png"); + background-image: url("@/assets/images/notifications/nitro_v3.png"); background-repeat: no-repeat; } \ No newline at end of file diff --git a/src/css/purse/PurseView.css b/src/css/purse/PurseView.css index 69c1732..7134551 100644 --- a/src/css/purse/PurseView.css +++ b/src/css/purse/PurseView.css @@ -22,7 +22,7 @@ pointer-events: all; } .borderhccontent{ - background-color: var(--ui-dark-bg, #212131); + background-color: #212131; border-radius: 0.5rem!important; border: 2px solid #383853; height: calc(100% - 3px); @@ -46,7 +46,7 @@ } .nitro-purse-seasonal-currency { - background-color: var(--ui-dark-bg, #212131); + background-color: #212131; background: linear-gradient(to right, #5f5f8d, transparent); height: 30px; margin-bottom: 4px; diff --git a/src/css/room/InfoStand.css b/src/css/room/InfoStand.css index 7e1a050..e44b062 100644 --- a/src/css/room/InfoStand.css +++ b/src/css/room/InfoStand.css @@ -27,7 +27,7 @@ width: clamp(160px, 20vw, 190px); /* Responsive width */ z-index: 30; pointer-events: auto; - background: var(--ui-dark-bg, #212131); + background: #212131; box-shadow: inset 0 5px rgba(38, 38, 57, 0.6), inset 0 -4px rgba(25, 25, 37, 0.6); border-radius: 0.5rem; padding: 10px; diff --git a/src/css/room/RoomWidgets.css b/src/css/room/RoomWidgets.css index b4d6ee2..093fa67 100644 --- a/src/css/room/RoomWidgets.css +++ b/src/css/room/RoomWidgets.css @@ -4,7 +4,7 @@ left: 15px; .nitro-room-tools { - background: var(--ui-dark-bg, #212131); + background: #212131; box-shadow: inset 0px 5px lighten(rgba(#000, .6), 2.5), inset 0 -4px darken(rgba(#000, .6), 4); border-top-right-radius: .25rem; border-bottom-right-radius: .25rem; @@ -54,7 +54,7 @@ } .nitro-room-history { - background: var(--ui-dark-bg, #212131); + background: #212131; box-shadow: inset 0px 5px lighten(rgba(#000, .6), 2.5), inset 0 -4px darken(rgba(#000, .6), 4); transition: all .2s ease; width: 150px; @@ -63,7 +63,7 @@ } .nitro-room-tools-info { - background: var(--ui-dark-bg, #212131); + background: #212131; box-shadow: inset 0px 5px lighten(rgba(#000, .6), 2.5), inset 0 -4px darken(rgba(#000, .6), 4); transition: all .2s ease; max-width: 250px; diff --git a/src/hooks/navigator/useNavigator.ts b/src/hooks/navigator/useNavigator.ts index 90df9fc..416c0af 100644 --- a/src/hooks/navigator/useNavigator.ts +++ b/src/hooks/navigator/useNavigator.ts @@ -1,4 +1,4 @@ -import { CanCreateRoomEventEvent, CantConnectMessageParser, CreateLinkEvent, DoorbellMessageEvent, FavouriteChangedEvent, FavouritesEvent, FlatAccessDeniedMessageEvent, FlatCreatedEvent, FollowFriendMessageComposer, GenericErrorEvent, GetGuestRoomMessageComposer, GetGuestRoomResultEvent, GetSessionDataManager, GetUserEventCatsMessageComposer, GetUserFlatCatsMessageComposer, HabboWebTools, LegacyExternalInterface, NavigatorCategoryDataParser, NavigatorEventCategoryDataParser, NavigatorHomeRoomEvent, NavigatorMetadataEvent, NavigatorOpenRoomCreatorEvent, NavigatorSavedSearch, NavigatorSearchesEvent, NavigatorSearchEvent, NavigatorSearchResultSet, NavigatorTopLevelContext, RoomDataParser, RoomDoorbellAcceptedEvent, RoomEnterErrorEvent, RoomEntryInfoMessageEvent, RoomForwardEvent, RoomScoreEvent, RoomSettingsUpdatedEvent, SecurityLevel, UserEventCatsEvent, UserFlatCatsEvent, UserInfoEvent, UserPermissionsEvent } from '@nitrots/nitro-renderer'; +import { CanCreateRoomEventEvent, CantConnectMessageParser, CreateLinkEvent, DoorbellMessageEvent, FavouriteChangedEvent, FavouritesEvent, FlatAccessDeniedMessageEvent, FlatCreatedEvent, FollowFriendMessageComposer, GenericErrorEvent, GetGuestRoomMessageComposer, GetGuestRoomResultEvent, GetRoomSessionManager, GetSessionDataManager, GetUserEventCatsMessageComposer, GetUserFlatCatsMessageComposer, HabboWebTools, LegacyExternalInterface, NavigatorCategoryDataParser, NavigatorEventCategoryDataParser, NavigatorHomeRoomEvent, NavigatorMetadataEvent, NavigatorOpenRoomCreatorEvent, NavigatorSavedSearch, NavigatorSearchesEvent, NavigatorSearchEvent, NavigatorSearchResultSet, NavigatorTopLevelContext, RoomDataParser, RoomDoorbellAcceptedEvent, RoomEnterErrorEvent, RoomEntryInfoMessageEvent, RoomForwardEvent, RoomScoreEvent, RoomSettingsUpdatedEvent, SecurityLevel, UserEventCatsEvent, UserFlatCatsEvent, UserInfoEvent, UserPermissionsEvent } from '@nitrots/nitro-renderer'; import { useState } from 'react'; import { useBetween } from 'use-between'; import { CreateRoomSession, DoorStateType, GetConfigurationValue, INavigatorData, LocalizeText, NotificationAlertType, SendMessageComposer, TryVisitRoom, VisitDesktop } from '../../api'; @@ -397,6 +397,8 @@ const useNavigatorState = () => return; } + if(GetRoomSessionManager().viewerSession) return; + let forwardType = -1; let forwardId = -1; diff --git a/src/hooks/notification/useNotification.ts b/src/hooks/notification/useNotification.ts index 4cd3f68..41e85d1 100644 --- a/src/hooks/notification/useNotification.ts +++ b/src/hooks/notification/useNotification.ts @@ -1,4 +1,4 @@ -import { AchievementNotificationMessageEvent, ActivityPointNotificationMessageEvent, ClubGiftNotificationEvent, ClubGiftSelectedEvent, ConnectionErrorEvent, GetLocalizationManager, GetRoomEngine, GetSessionDataManager, HabboBroadcastMessageEvent, HotelClosedAndOpensEvent, HotelClosesAndWillOpenAtEvent, HotelWillCloseInMinutesEvent, InfoFeedEnableMessageEvent, MaintenanceStatusMessageEvent, ModeratorCautionEvent, ModeratorMessageEvent, MOTDNotificationEvent, NotificationDialogMessageEvent, PetLevelNotificationEvent, PetReceivedMessageEvent, RespectReceivedEvent, RoomEnterEffect, RoomEnterEvent, SimpleAlertMessageEvent, UserBannedMessageEvent, Vector3d } from '@nitrots/nitro-renderer'; +import { AchievementNotificationMessageEvent, ActivityPointNotificationMessageEvent, ClubGiftNotificationEvent, ClubGiftSelectedEvent, ConnectionErrorEvent, GetLocalizationManager, GetRoomEngine, GetSessionDataManager, HabboBroadcastMessageEvent, HotelClosedAndOpensEvent, HotelClosesAndWillOpenAtEvent, HotelWillCloseInMinutesEvent, InfoFeedEnableMessageEvent, MaintenanceStatusMessageEvent, ModeratorCautionEvent, ModeratorMessageEvent, MOTDNotificationEvent, NotificationDialogMessageEvent, PetLevelNotificationEvent, PetReceivedMessageEvent, RespectReceivedEvent, RoomEnterEffect, RoomEnterEvent, SimpleAlertMessageEvent, UserBannedMessageEvent, Vector3d, WiredRewardResultMessageEvent } from '@nitrots/nitro-renderer'; import { useCallback, useState } from 'react'; import { useBetween } from 'use-between'; import { GetConfigurationValue, LocalizeBadgeName, LocalizeText, NotificationAlertItem, NotificationAlertType, NotificationBubbleItem, NotificationBubbleType, NotificationConfirmItem, PlaySound, ProductImageUtility, TradingNotificationType } from '../../api'; @@ -397,6 +397,28 @@ const useNotificationState = () => simpleAlert(LocalizeText(parser.alertMessage), NotificationAlertType.DEFAULT, null, null, LocalizeText(parser.titleMessage ? parser.titleMessage : 'notifications.broadcast.title')); }); + useMessageEvent(WiredRewardResultMessageEvent, event => + { + const parser = event.getParser(); + + switch(parser.reason) + { + case WiredRewardResultMessageEvent.PRODUCT_DONATED_CODE: + case WiredRewardResultMessageEvent.BADGE_DONATED_CODE: + simpleAlert(LocalizeText('wiredfurni.rewardsuccess.body'), NotificationAlertType.DEFAULT, null, null, LocalizeText('wiredfurni.rewardsuccess.title')); + return; + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 8: + simpleAlert(LocalizeText(`wiredfurni.rewardfailed.reason.${ parser.reason }`), NotificationAlertType.DEFAULT, null, null, LocalizeText('wiredfurni.rewardfailed.title')); + return; + } + }); + const onRoomEnterEvent = useCallback(() => { if(modDisclaimerShown) return; diff --git a/src/hooks/rooms/useRoom.ts b/src/hooks/rooms/useRoom.ts index 743ce87..157ab03 100644 --- a/src/hooks/rooms/useRoom.ts +++ b/src/hooks/rooms/useRoom.ts @@ -1,7 +1,7 @@ import { ColorConverter, GetRenderer, GetRoomEngine, GetStage, IRoomSession, NitroAdjustmentFilter, NitroSprite, NitroTexture, RoomBackgroundColorEvent, RoomEngineEvent, RoomEngineObjectEvent, RoomGeometry, RoomId, RoomObjectCategory, RoomObjectHSLColorEnabledEvent, RoomObjectOperationType, RoomSessionEvent, RoomVariableEnum, Vector3d } from '@nitrots/nitro-renderer'; import { useEffect, useState } from 'react'; import { useBetween } from 'use-between'; -import { CanManipulateFurniture, DispatchUiEvent, GetRoomSession, InitializeRoomInstanceRenderingCanvas, IsFurnitureSelectionDisabled, ProcessRoomObjectOperation, RoomWidgetUpdateBackgroundColorPreviewEvent, RoomWidgetUpdateRoomObjectEvent, SetActiveRoomId, StartRoomSession } from '../../api'; +import { CanManipulateFurniture, DispatchUiEvent, GetRoomSession, IsFurnitureSelectionDisabled, ProcessRoomObjectOperation, RoomWidgetUpdateBackgroundColorPreviewEvent, RoomWidgetUpdateRoomObjectEvent, SetActiveRoomId, StartRoomSession } from '../../api'; import { useNitroEvent, useUiEvent } from '../events'; const useRoomState = () => @@ -253,15 +253,20 @@ const useRoomState = () => const resize = (event: UIEvent) => { - const width = Math.floor(window.innerWidth); - const height = Math.floor(window.innerHeight); + const newWidth = Math.floor(window.innerWidth); + const newHeight = Math.floor(window.innerHeight); - renderer.resize(width, height, window.devicePixelRatio); + const offsetX = canvas.screenOffsetX - (newWidth - canvas.width) / 2; + const offsetY = canvas.screenOffsetY - (newHeight - canvas.height) / 2; - background.width = width; - background.height = height; + renderer.resize(newWidth, newHeight, window.devicePixelRatio); - InitializeRoomInstanceRenderingCanvas(width, height, 1); + background.width = newWidth; + background.height = newHeight; + + canvas.initialize(newWidth, newHeight); + canvas.screenOffsetX = ~~offsetX; + canvas.screenOffsetY = ~~offsetY; }; window.addEventListener('resize', resize); diff --git a/src/hooks/rooms/widgets/useChatWidget.ts b/src/hooks/rooms/widgets/useChatWidget.ts index d430a38..648a102 100644 --- a/src/hooks/rooms/widgets/useChatWidget.ts +++ b/src/hooks/rooms/widgets/useChatWidget.ts @@ -123,9 +123,10 @@ const useChatWidgetState = () => text = LocalizeText('widget.chatbubble.handitem', ['username', 'handitem'], [username, LocalizeText(('handitem' + event.extraParam))]); break; case RoomSessionChatEvent.CHAT_TYPE_MUTE_REMAINING: { - const hours = ((event.extraParam > 0) ? Math.floor((event.extraParam / 3600)) : 0).toString(); - const minutes = ((event.extraParam > 0) ? Math.floor((event.extraParam % 3600) / 60) : 0).toString(); - const seconds = (event.extraParam % 60).toString(); + const remainingSeconds = Math.max(0, event.extraParam); + const hours = Math.floor(remainingSeconds / 3600).toString(); + const minutes = Math.floor((remainingSeconds % 3600) / 60).toString(); + const seconds = (remainingSeconds % 60).toString(); text = LocalizeText('widget.chatbubble.mutetime', ['hours', 'minutes', 'seconds'], [hours, minutes, seconds]); break; diff --git a/src/hooks/wired/useWired.ts b/src/hooks/wired/useWired.ts index b0c3f32..19e6ed8 100644 --- a/src/hooks/wired/useWired.ts +++ b/src/hooks/wired/useWired.ts @@ -235,6 +235,7 @@ const useWiredState = () => { const parser = event.getParser(); + WiredSelectionVisualizer.clearAllSelectionShaders(); setTrigger(null); }); @@ -275,6 +276,7 @@ const useWiredState = () => return () => { + WiredSelectionVisualizer.clearAllSelectionShaders(); setIntParams([]); setStringParam(''); setActionDelay(0);