diff --git a/package-lock.json b/package-lock.json index db6db9b4..0bfc4511 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2166,6 +2166,11 @@ "resolved": "https://registry.npmjs.org/@pixi/tilemap/-/tilemap-3.2.2.tgz", "integrity": "sha512-svdmMyJP63vdae3t66tCmE8IWeO/6lD1xXU+5gzfxqxJS5seTp2bm8mQok2c8PF0O6l/NYlLz6BRklOuEuHboQ==" }, + "@pixi/tilemap": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@pixi/tilemap/-/tilemap-3.2.2.tgz", + "integrity": "sha512-svdmMyJP63vdae3t66tCmE8IWeO/6lD1xXU+5gzfxqxJS5seTp2bm8mQok2c8PF0O6l/NYlLz6BRklOuEuHboQ==" + }, "@pixi/utils": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/@pixi/utils/-/utils-6.2.0.tgz", diff --git a/public/renderer-config.json.default b/public/renderer-config.json.default index 48c69d16..34371d8d 100644 --- a/public/renderer-config.json.default +++ b/public/renderer-config.json.default @@ -5,7 +5,7 @@ "hof.furni.url": "HOF_FURNI_URL", "images.url": "${asset.url}/images", "gamedata.url": "${asset.url}/gamedata", - "sounds.url": "${asset.url}/sounds", + "sounds.url": "${asset.url}/sounds/%sample%.mp3", "external.texts.url": [ "${gamedata.url}/ExternalTexts.json", "${gamedata.url}/UITexts.json" ], "external.samples.url": "${hof.furni.url}/mp3/sound_machine_sample_%sample%.mp3", "furnidata.url": "${gamedata.url}/FurnitureData.json", diff --git a/src/api/utils/PlaySound.ts b/src/api/utils/PlaySound.ts new file mode 100644 index 00000000..96bc8fc5 --- /dev/null +++ b/src/api/utils/PlaySound.ts @@ -0,0 +1,14 @@ +import { NitroSoundEvent } from '@nitrots/nitro-renderer/src/nitro/events/NitroSoundEvent'; +import { dispatchMainEvent } from '../../hooks'; + +export function PlaySound(sampleCode: string): void +{ + dispatchMainEvent(new NitroSoundEvent(NitroSoundEvent.PLAY_SOUND, sampleCode)); +} + +export const CAMERA_SHUTTER = 'camera_shutter'; +export const CREDITS = 'credits'; +export const DUCKETS = 'duckets'; +export const MESSENGER_NEW_THREAD = 'messenger_new_thread'; +export const MESSENGER_MESSAGE_RECEIVED = 'messenger_message_received'; +export const MODTOOLS_NEW_TICKET = 'modtools_new_ticket'; diff --git a/src/assets/images/campaign/available.png b/src/assets/images/campaign/available.png new file mode 100644 index 00000000..1cc8fa62 Binary files /dev/null and b/src/assets/images/campaign/available.png differ diff --git a/src/assets/images/campaign/campaign_day_generic_bg.png b/src/assets/images/campaign/campaign_day_generic_bg.png new file mode 100644 index 00000000..25b3c622 Binary files /dev/null and b/src/assets/images/campaign/campaign_day_generic_bg.png differ diff --git a/src/assets/images/campaign/campaign_opened.png b/src/assets/images/campaign/campaign_opened.png new file mode 100644 index 00000000..9ec1bb30 Binary files /dev/null and b/src/assets/images/campaign/campaign_opened.png differ diff --git a/src/assets/images/campaign/locked.png b/src/assets/images/campaign/locked.png new file mode 100644 index 00000000..3805e502 Binary files /dev/null and b/src/assets/images/campaign/locked.png differ diff --git a/src/assets/images/campaign/locked_bg.png b/src/assets/images/campaign/locked_bg.png new file mode 100644 index 00000000..93927dd5 Binary files /dev/null and b/src/assets/images/campaign/locked_bg.png differ diff --git a/src/assets/images/campaign/next.png b/src/assets/images/campaign/next.png new file mode 100644 index 00000000..88a2883a Binary files /dev/null and b/src/assets/images/campaign/next.png differ diff --git a/src/assets/images/campaign/prev.png b/src/assets/images/campaign/prev.png new file mode 100644 index 00000000..914a6592 Binary files /dev/null and b/src/assets/images/campaign/prev.png differ diff --git a/src/assets/images/campaign/unavailable.png b/src/assets/images/campaign/unavailable.png new file mode 100644 index 00000000..dc134c3f Binary files /dev/null and b/src/assets/images/campaign/unavailable.png differ diff --git a/src/assets/images/campaign/unlocked_bg.png b/src/assets/images/campaign/unlocked_bg.png new file mode 100644 index 00000000..31c35ba1 Binary files /dev/null and b/src/assets/images/campaign/unlocked_bg.png differ diff --git a/src/layout/card/header/NitroCardHeaderView.scss b/src/layout/card/header/NitroCardHeaderView.scss index ded3c2bd..fe6c9d8e 100644 --- a/src/layout/card/header/NitroCardHeaderView.scss +++ b/src/layout/card/header/NitroCardHeaderView.scss @@ -16,11 +16,6 @@ } } - &.theme-dark { - background-color: #3d5f6e !important; - color: #fff; - } - .bg-tertiary-split { position: relative; border-bottom: 2px solid darken($quaternary, 5); diff --git a/src/layout/mini-camera/NitroLayoutMiniCameraView.tsx b/src/layout/mini-camera/NitroLayoutMiniCameraView.tsx index 7b5c6fc9..261eebb3 100644 --- a/src/layout/mini-camera/NitroLayoutMiniCameraView.tsx +++ b/src/layout/mini-camera/NitroLayoutMiniCameraView.tsx @@ -1,6 +1,7 @@ import { NitroRectangle } from '@nitrots/nitro-renderer'; import { FC, useCallback, useRef } from 'react'; import { GetRoomEngine, LocalizeText } from '../../api'; +import { CAMERA_SHUTTER, PlaySound } from '../../api/utils/PlaySound'; import { DraggableWindow } from '../draggable-window'; import { NitroLayoutMiniCameraViewProps } from './NitroLayoutMiniCameraView.types'; @@ -20,6 +21,7 @@ export const NitroLayoutMiniCameraView: FC = pro const takePicture = useCallback(() => { + PlaySound(CAMERA_SHUTTER); textureReceiver(GetRoomEngine().createTextureFromRoom(roomId, 1, getCameraBounds())); }, [ roomId, getCameraBounds, textureReceiver ]); diff --git a/src/views/Styles.scss b/src/views/Styles.scss index a727aa12..4a7438ed 100644 --- a/src/views/Styles.scss +++ b/src/views/Styles.scss @@ -24,3 +24,4 @@ @import './help/HelpView'; @import './floorplan-editor/FloorplanEditorView'; @import './nitropedia/NitropediaView'; +@import './campaign/CampaignView'; diff --git a/src/views/camera/views/capture/CameraWidgetCaptureView.tsx b/src/views/camera/views/capture/CameraWidgetCaptureView.tsx index 40407e3e..1c369a78 100644 --- a/src/views/camera/views/capture/CameraWidgetCaptureView.tsx +++ b/src/views/camera/views/capture/CameraWidgetCaptureView.tsx @@ -1,6 +1,7 @@ import { NitroRectangle, TextureUtils } from '@nitrots/nitro-renderer'; import { FC, useCallback, useRef } from 'react'; import { GetRoomEngine, GetRoomSession, LocalizeText } from '../../../../api'; +import { CAMERA_SHUTTER, PlaySound } from '../../../../api/utils/PlaySound'; import { DraggableWindow } from '../../../../layout'; import { CameraPicture } from '../../common/CameraPicture'; import { useCameraWidgetContext } from '../../context/CameraWidgetContext'; @@ -30,7 +31,6 @@ export const CameraWidgetCaptureView: FC = props = if(selectedPictureIndex > -1) { setSelectedPictureIndex(-1); - return; } @@ -45,6 +45,7 @@ export const CameraWidgetCaptureView: FC = props = clone.pop(); } + PlaySound(CAMERA_SHUTTER); clone.push(new CameraPicture(texture, TextureUtils.generateImageUrl(texture))); setCameraRoll(clone); diff --git a/src/views/campaign/CampaignView.scss b/src/views/campaign/CampaignView.scss new file mode 100644 index 00000000..506d0169 --- /dev/null +++ b/src/views/campaign/CampaignView.scss @@ -0,0 +1 @@ +@import './views/calendar/CalendarView'; diff --git a/src/views/campaign/CampaignView.tsx b/src/views/campaign/CampaignView.tsx new file mode 100644 index 00000000..2178333d --- /dev/null +++ b/src/views/campaign/CampaignView.tsx @@ -0,0 +1,113 @@ +import { CampaignCalendarData, CampaignCalendarDataMessageEvent, CampaignCalendarDoorOpenedMessageEvent, OpenCampaignCalendarDoorAsStaffComposer, OpenCampaignCalendarDoorComposer } from '@nitrots/nitro-renderer'; +import { FC, useCallback, useEffect, useState } from 'react'; +import { AddEventLinkTracker, RemoveLinkEventTracker } from '../../api'; +import { BatchUpdates, CreateMessageHook, SendMessageHook } from '../../hooks'; +import { CalendarView } from './views/calendar/CalendarView'; + +export const CampaignView: FC<{}> = props => +{ + const [ calendarData, setCalendarData ] = useState(null); + const [ lastOpenAttempt, setLastOpenAttempt ] = useState(-1); + const [ receivedProducts, setReceivedProducts ] = useState>(new Map()); + const [ isCalendarOpen, setCalendarOpen ] = useState(false); + + const onCampaignCalendarDataMessageEvent = useCallback((event: CampaignCalendarDataMessageEvent) => + { + const parser = event.getParser(); + + if(!parser) return; + setCalendarData(parser.calendarData); + }, []); + + CreateMessageHook(CampaignCalendarDataMessageEvent, onCampaignCalendarDataMessageEvent); + + const onCampaignCalendarDoorOpenedMessageEvent = useCallback((event: CampaignCalendarDoorOpenedMessageEvent) => + { + const parser = event.getParser(); + + if(!parser) return; + + const lastAttempt = lastOpenAttempt; + + if(parser.doorOpened) + { + BatchUpdates(() => + { + setCalendarData(prev => + { + const copy = prev.clone(); + copy.openedDays.push(lastOpenAttempt); + + return copy; + }); + + setReceivedProducts(prev => + { + const copy = new Map(prev); + copy.set(lastAttempt, parser.furnitureClassName); + + return copy; + }); + }); + } + + setLastOpenAttempt(-1); + }, [lastOpenAttempt]); + + CreateMessageHook(CampaignCalendarDoorOpenedMessageEvent, onCampaignCalendarDoorOpenedMessageEvent); + + const openPackage = useCallback((id: number, asStaff = false) => + { + if(!calendarData) return; + + setLastOpenAttempt(id); + + if(asStaff) + { + SendMessageHook(new OpenCampaignCalendarDoorAsStaffComposer(calendarData.campaignName, id)); + } + + else + { + SendMessageHook(new OpenCampaignCalendarDoorComposer(calendarData.campaignName, id)); + } + }, [calendarData]); + + const onCalendarClose = useCallback(() => + { + setCalendarOpen(false); + }, []); + + const onLinkReceived = useCallback((link: string) => + { + const value = link.split('/'); + + if(value.length < 2) return; + + switch(value[1]) + { + case 'calendar': + setCalendarOpen(true); + break; + } + }, []); + + useEffect(() => + { + const linkTracker = { linkReceived: onLinkReceived, eventUrlPrefix: 'openView/' }; + AddEventLinkTracker(linkTracker); + + return () => + { + RemoveLinkEventTracker(linkTracker); + } + }, [onLinkReceived]); + + return ( + <> + {(calendarData && isCalendarOpen) && + + } + + ) +} diff --git a/src/views/campaign/common/CalendarItemState.ts b/src/views/campaign/common/CalendarItemState.ts new file mode 100644 index 00000000..1b91ca3f --- /dev/null +++ b/src/views/campaign/common/CalendarItemState.ts @@ -0,0 +1,7 @@ +export class CalendarItemState +{ + public static readonly STATE_UNLOCKED = 1; + public static readonly STATE_LOCKED_AVAILABLE = 2; + public static readonly STATE_LOCKED_EXPIRED = 3; + public static readonly STATE_LOCKED_FUTURE = 4; +} diff --git a/src/views/campaign/common/Utils.ts b/src/views/campaign/common/Utils.ts new file mode 100644 index 00000000..7b7be678 --- /dev/null +++ b/src/views/campaign/common/Utils.ts @@ -0,0 +1,4 @@ +export const getNumItemsDisplayed = (): number => +{ + return Math.min(Math.max(2, Math.floor(window.screen.width / 135) - 3), 7); +} diff --git a/src/views/campaign/views/calendar-item/CalendarItemView.tsx b/src/views/campaign/views/calendar-item/CalendarItemView.tsx new file mode 100644 index 00000000..fe2eba0c --- /dev/null +++ b/src/views/campaign/views/calendar-item/CalendarItemView.tsx @@ -0,0 +1,50 @@ +import { FC, useCallback } from 'react'; +import { GetRoomEngine, GetSessionDataManager } from '../../../../api'; +import { NitroLayoutFlexColumn } from '../../../../layout'; +import { CalendarItemState } from '../../common/CalendarItemState'; +import { CalendarItemViewProps } from './CalendarItemView.types'; + +export const CalendarItemView: FC = props => +{ + const { state = null, productName = null, active = false, onClick = null, id = null } = props; + + const getFurnitureIcon = useCallback((name: string) => + { + + let furniData = GetSessionDataManager().getFloorItemDataByName(name); + let url = null; + if(furniData) url = GetRoomEngine().getFurnitureFloorIconUrl(furniData.id); + else + { + furniData = GetSessionDataManager().getWallItemDataByName(name); + if(furniData) url = GetRoomEngine().getFurnitureWallIconUrl(furniData.id); + } + + return url; + }, []); + + return ( + onClick(id)}> + { (state === CalendarItemState.STATE_UNLOCKED) && +
+
+ { productName && + + } +
+
+ } + + { (state !== CalendarItemState.STATE_UNLOCKED) && +
+ { (state === CalendarItemState.STATE_LOCKED_AVAILABLE) && +
+ } + { (state === CalendarItemState.STATE_LOCKED_EXPIRED || state === CalendarItemState.STATE_LOCKED_FUTURE) && +
+ } +
+ } + + ); +} diff --git a/src/views/campaign/views/calendar-item/CalendarItemView.types.ts b/src/views/campaign/views/calendar-item/CalendarItemView.types.ts new file mode 100644 index 00000000..038dc03c --- /dev/null +++ b/src/views/campaign/views/calendar-item/CalendarItemView.types.ts @@ -0,0 +1,8 @@ +export interface CalendarItemViewProps +{ + id: number; + productName?: string; + state: number; + active?: boolean; + onClick(itemId: number): void; +} diff --git a/src/views/campaign/views/calendar/CalendarView.scss b/src/views/campaign/views/calendar/CalendarView.scss new file mode 100644 index 00000000..f621d2af --- /dev/null +++ b/src/views/campaign/views/calendar/CalendarView.scss @@ -0,0 +1,68 @@ +.nitro-campaign-calendar { + width: 1055px; + height: 400px; + + .calendar-item { + background: url("../../../../assets/images/campaign/campaign_day_generic_bg.png"); + max-height: 100%; + min-width: 135px; + filter: brightness(80%); + + &.active { + filter: brightness(100%); + } + + .unlocked-generic-bg { + background: url("../../../../assets/images/campaign/unlocked_bg.png"); + background-size: 132px 132px; + width: 132px; + height: 132px; + + //width: 190px; + //height: 189px; + + .opened { + background: url("../../../../assets/images/campaign/campaign_opened.png"); + width: 96px; + height: 66px; + + .furni-icon + { + margin-bottom: 10px; + } + } + } + + .locked-generic-bg { + background: url("../../../../assets/images/campaign/locked_bg.png"); + width: 132px; + height: 132px; + + .available { + background: url("../../../../assets/images/campaign/available.png"); + width: 69px; + height: 78px; + } + + .unavailable { + background: url("../../../../assets/images/campaign/unavailable.png"); + width: 68px; + height: 78px; + } + } + } + + .calendar-prev { + background: url("../../../../assets/images/campaign/prev.png"); + width: 33px; + min-width: 33px; + height: 34px; + } + + .calendar-next { + background: url("../../../../assets/images/campaign/next.png"); + width: 33px; + min-width: 33px; + height: 34px; + } +} diff --git a/src/views/campaign/views/calendar/CalendarView.tsx b/src/views/campaign/views/calendar/CalendarView.tsx new file mode 100644 index 00000000..b6566791 --- /dev/null +++ b/src/views/campaign/views/calendar/CalendarView.tsx @@ -0,0 +1,134 @@ +import { FC, useCallback, useState } from 'react'; +import { GetSessionDataManager, LocalizeText } from '../../../../api'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroLayoutFlex } from '../../../../layout'; +import { CalendarItemState } from '../../common/CalendarItemState'; +import { getNumItemsDisplayed } from '../../common/Utils'; +import { CalendarItemView } from '../calendar-item/CalendarItemView'; +import { CalendarViewProps } from './CalendarView.types'; + +export const CalendarView: FC = props => +{ + const { close = null, campaignName = null, currentDay = null, numDays = null, missedDays = null, openedDays = null, openPackage = null, receivedProducts = null } = props; + const [selectedDay, setSelectedDay] = useState(currentDay); + const [index, setIndex] = useState(Math.max(0, selectedDay - 1)); + + const getDayState = useCallback((day: number) => + { + if(openedDays.includes(day)) + { + return CalendarItemState.STATE_UNLOCKED; + } + if(day > currentDay) + { + return CalendarItemState.STATE_LOCKED_FUTURE; + } + + if(missedDays.includes(day)) + { + return CalendarItemState.STATE_LOCKED_EXPIRED; + } + + return CalendarItemState.STATE_LOCKED_AVAILABLE; + }, [currentDay, missedDays, openedDays]); + + const dayMessage = useCallback((day: number) => + { + const state = getDayState(day); + + switch(state) + { + case CalendarItemState.STATE_UNLOCKED: + return LocalizeText('campaign.calendar.info.unlocked'); + case CalendarItemState.STATE_LOCKED_FUTURE: + return LocalizeText('campaign.calendar.info.future'); + case CalendarItemState.STATE_LOCKED_EXPIRED: + return LocalizeText('campaign.calendar.info.expired'); + default: return LocalizeText('campaign.calendar.info.available.desktop'); + } + }, [getDayState]); + + const onClickNext = useCallback(() => + { + const nextDay = selectedDay + 1; + + if((nextDay) === numDays) return; + + setSelectedDay(nextDay); + + if((index + getNumItemsDisplayed()) < nextDay + 1) + { + setIndex(index + 1); + } + + }, [index, numDays, selectedDay]); + + const onClickPrev = useCallback(() => + { + const prevDay = selectedDay - 1; + + if((prevDay < 0)) return; + + setSelectedDay(prevDay); + + if(index > prevDay) + { + setIndex(index - 1); + } + }, [index, selectedDay]); + + const onClickItem = useCallback((item: number) => + { + if(selectedDay === item) + { + //handle opening + const state = getDayState(item); + if(state === CalendarItemState.STATE_LOCKED_AVAILABLE) openPackage(item, false); + } + else + { + setSelectedDay(item); + } + }, [getDayState, openPackage, selectedDay]); + + const forceOpen = useCallback(() => + { + const id = selectedDay; + const state = getDayState(id); + if(GetSessionDataManager().isModerator && state !== CalendarItemState.STATE_UNLOCKED) + { + openPackage(id, true); + } + }, [getDayState, openPackage, selectedDay]); + + return ( + + + +
+
+

{LocalizeText('campaign.calendar.heading.day', ['number'], [(selectedDay + 1).toString()])}

+

{dayMessage(selectedDay)}

+
+ {GetSessionDataManager().isModerator && + + } +
+
+ + +
+ +
+ { + [...Array(getNumItemsDisplayed())].map((e, i) => + { + const day = index + i; + return + }) + } +
+ + + + ) +} diff --git a/src/views/campaign/views/calendar/CalendarView.types.ts b/src/views/campaign/views/calendar/CalendarView.types.ts new file mode 100644 index 00000000..454021e3 --- /dev/null +++ b/src/views/campaign/views/calendar/CalendarView.types.ts @@ -0,0 +1,11 @@ +export interface CalendarViewProps +{ + close(): void; + openPackage(id: number, asStaff: boolean): void; + receivedProducts: Map; + campaignName: string; + currentDay: number; + numDays: number; + openedDays: number[]; + missedDays: number[]; +} diff --git a/src/views/catalog/CatalogView.tsx b/src/views/catalog/CatalogView.tsx index 5c30cac0..850dc1bb 100644 --- a/src/views/catalog/CatalogView.tsx +++ b/src/views/catalog/CatalogView.tsx @@ -1,6 +1,7 @@ import { GetCatalogIndexComposer, GetCatalogPageComposer, GetGiftWrappingConfigurationComposer, ILinkEventTracker, INodeData, RoomPreviewer } from '@nitrots/nitro-renderer'; import { FC, useCallback, useEffect, useReducer, useState } from 'react'; import { AddEventLinkTracker, GetRoomEngine, LocalizeText, RemoveLinkEventTracker } from '../../api'; +import { CREDITS, PlaySound } from '../../api/utils/PlaySound'; import { Column } from '../../common/Column'; import { Grid } from '../../common/Grid'; import { CatalogEvent } from '../../events'; @@ -49,6 +50,9 @@ export const CatalogView: FC = props => save = true; setIsVisible(value => !value); return; + case CatalogEvent.PURCHASE_SUCCESS: + PlaySound(CREDITS); + return; } if(save) saveActivePages(); @@ -58,6 +62,7 @@ export const CatalogView: FC = props => useUiEvent(CatalogEvent.HIDE_CATALOG, onCatalogEvent); useUiEvent(CatalogEvent.TOGGLE_CATALOG, onCatalogEvent); useUiEvent(CatalogEvent.CATALOG_RESET, onCatalogEvent); + useUiEvent(CatalogEvent.PURCHASE_SUCCESS, onCatalogEvent); const linkReceived = useCallback((url: string) => { diff --git a/src/views/chat-history/ChatHistoryView.scss b/src/views/chat-history/ChatHistoryView.scss index 9f8e6385..84bafc9f 100644 --- a/src/views/chat-history/ChatHistoryView.scss +++ b/src/views/chat-history/ChatHistoryView.scss @@ -2,6 +2,15 @@ width: $chat-history-width; height: $chat-history-height; + background-color: #1C323F; + border: 2px solid rgba(255, 255, 255, 0.5); + border-radius: 0.25rem; + + .nitro-card-header-container { + background-color: #3d5f6e; + color: #fff; + } + .chat-history-content { .chat-history-container { min-height: 200px; diff --git a/src/views/floorplan-editor/FloorplanEditorView.scss b/src/views/floorplan-editor/FloorplanEditorView.scss index b11b5107..7bc41e59 100644 --- a/src/views/floorplan-editor/FloorplanEditorView.scss +++ b/src/views/floorplan-editor/FloorplanEditorView.scss @@ -6,7 +6,7 @@ width: 100%; height: 300px; min-height: 300px; - overflow-x: scroll; + overflow: scroll; } .color { diff --git a/src/views/floorplan-editor/common/FloorplanEditor.ts b/src/views/floorplan-editor/common/FloorplanEditor.ts index 99cdd12c..71fcbed9 100644 --- a/src/views/floorplan-editor/common/FloorplanEditor.ts +++ b/src/views/floorplan-editor/common/FloorplanEditor.ts @@ -43,7 +43,7 @@ export class FloorplanEditor extends PixiApplicationProxy this._width = 0; this._height = 0; this._isHolding = false; - this._lastUsedTile = new NitroPoint(-1,-1); + this._lastUsedTile = new NitroPoint(-1, -1); this._actionSettings = new ActionSettings(); } @@ -52,7 +52,7 @@ export class FloorplanEditor extends PixiApplicationProxy if(!this._isInitialized) { this.loader.add('tiles', GetConfiguration('floorplan.tile.url')); - + this.loader.load((_, resources) => { this._tilemapRenderer = new NitroTilemap(resources['tiles'].spritesheet.baseTexture); @@ -127,9 +127,8 @@ export class FloorplanEditor extends PixiApplicationProxy const bufIndex = j + bufSize; const data = buffer.slice(j, bufIndex); - const width = data[4]; - const height = data[5]; - + const width = TILE_SIZE; + const height = TILE_SIZE / 2; const mousePositionX = Math.floor(tempPoint.x); const mousePositionY = Math.floor(tempPoint.y); @@ -137,12 +136,11 @@ export class FloorplanEditor extends PixiApplicationProxy const tileStartX = data[2]; const tileStartY = data[3]; - const centreX = tileStartX + (width / 2); const centreY = tileStartY + (height / 2); - const dx = Math.abs(mousePositionX - centreX - 2); - const dy = Math.abs(mousePositionY - centreY - 2); + const dx = Math.abs(mousePositionX - centreX); + const dy = Math.abs(mousePositionY - centreY); const solution = (dx / (width * 0.5) + dy / (height * 0.5) <= 1);//todo: improve this if(solution) @@ -171,7 +169,6 @@ export class FloorplanEditor extends PixiApplicationProxy return false; } - private onClick(x: number, y: number): void { const tile = this._tilemap[y][x]; @@ -212,7 +209,7 @@ export class FloorplanEditor extends PixiApplicationProxy { if((x + 1) > this._width) this._width = x + 1; - if( (y + 1) > this._height) this._height = y + 1; + if((y + 1) > this._height) this._height = y + 1; } const newHeight = HEIGHT_SCHEME[futureHeightIndex]; @@ -229,7 +226,7 @@ export class FloorplanEditor extends PixiApplicationProxy public renderTiles(): void { this.tilemapRenderer.clear(); - + for(let y = 0; y < this._tilemap.length; y++) { for(let x = 0; x < this.tilemap[y].length; x++) @@ -237,13 +234,13 @@ export class FloorplanEditor extends PixiApplicationProxy const tile = this.tilemap[y][x]; let assetName = tile.height; - if(this._doorLocation.x === x && this._doorLocation.y === y) + if(this._doorLocation.x === x && this._doorLocation.y === y) assetName = FloorplanEditor.TILE_DOOR; if(tile.isBlocked) assetName = FloorplanEditor.TILE_BLOCKED; - + //if((tile.height === 'x') || tile.height === 'X') continue; - const [positionX, positionY ] = getScreenPositionForTile(x, y); + const [positionX, positionY] = getScreenPositionForTile(x, y); this._tilemapRenderer.tile(`${assetName}.png`, positionX, positionY); } } @@ -337,10 +334,10 @@ export class FloorplanEditor extends PixiApplicationProxy if(tile.height !== 'x') { - if( (x + 1) > this._width) + if((x + 1) > this._width) this._width = x + 1; - if( (y + 1) > this._height) + if((y + 1) > this._height) this._height = y + 1; } } diff --git a/src/views/friends/views/messenger/FriendsMessengerView.tsx b/src/views/friends/views/messenger/FriendsMessengerView.tsx index 7faec597..0d5ad76c 100644 --- a/src/views/friends/views/messenger/FriendsMessengerView.tsx +++ b/src/views/friends/views/messenger/FriendsMessengerView.tsx @@ -1,6 +1,7 @@ import { FollowFriendMessageComposer, ILinkEventTracker, NewConsoleMessageEvent, SendMessageComposer } from '@nitrots/nitro-renderer'; import { FC, KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { AddEventLinkTracker, GetUserProfile, LocalizeText, RemoveLinkEventTracker } from '../../../../api'; +import { MESSENGER_MESSAGE_RECEIVED, MESSENGER_NEW_THREAD, PlaySound } from '../../../../api/utils/PlaySound'; import { FriendsMessengerIconEvent } from '../../../../events'; import { BatchUpdates, CreateMessageHook, dispatchUiEvent, SendMessageHook } from '../../../../hooks'; import { NitroCardContentView, NitroCardHeaderView, NitroCardView, NitroLayoutButton, NitroLayoutButtonGroup, NitroLayoutFlex, NitroLayoutFlexColumn } from '../../../../layout'; @@ -114,6 +115,8 @@ export const FriendsMessengerView: FC<{}> = props => SendMessageHook(new SendMessageComposer(thread.participant.id, messageText)); + if(messageThreads.length === 1 && thread.groups.length === 1) PlaySound(MESSENGER_NEW_THREAD); + thread.addMessage(0, messageText, 0, null, MessengerThreadChat.CHAT); BatchUpdates(() => @@ -227,6 +230,8 @@ export const FriendsMessengerView: FC<{}> = props => } } + if(isUnread) PlaySound(MESSENGER_MESSAGE_RECEIVED); + dispatchUiEvent(new FriendsMessengerIconEvent(FriendsMessengerIconEvent.UPDATE_ICON, isUnread ? FriendsMessengerIconEvent.UNREAD_ICON : FriendsMessengerIconEvent.SHOW_ICON)); }, [ visibleThreads, updateValue ]); diff --git a/src/views/help/HelpMessageHandler.tsx b/src/views/help/HelpMessageHandler.tsx index 959abdb9..047dd512 100644 --- a/src/views/help/HelpMessageHandler.tsx +++ b/src/views/help/HelpMessageHandler.tsx @@ -1,9 +1,10 @@ -import { CallForHelpResultMessageEvent } from '@nitrots/nitro-renderer'; +import { CallForHelpResultMessageEvent, GetPendingCallsForHelpMessageComposer, IssueCloseNotificationMessageEvent } from '@nitrots/nitro-renderer'; import { FC, useCallback } from 'react'; import { LocalizeText } from '../../api'; -import { CreateMessageHook } from '../../hooks/messages/message-event'; +import { CreateMessageHook, SendMessageHook } from '../../hooks/messages/message-event'; import { NotificationAlertType } from '../notification-center/common/NotificationAlertType'; import { NotificationUtilities } from '../notification-center/common/NotificationUtilities'; +import { CallForHelpResult } from './common/CallForHelpResult'; import { GetCloseReasonKey } from './common/GetCloseReasonKey'; export const HelpMessageHandler: FC<{}> = props => @@ -14,12 +15,36 @@ export const HelpMessageHandler: FC<{}> = props => let message = parser.messageText; - if(!message || !message.length) message = LocalizeText('help.cfh.closed.' + GetCloseReasonKey(parser.resultType)) - - NotificationUtilities.simpleAlert(message, NotificationAlertType.MODERATION, null, null, LocalizeText('mod.alert.title')); + switch(parser.resultType) + { + case CallForHelpResult.TOO_MANY_PENDING_CALLS_CODE: + SendMessageHook(new GetPendingCallsForHelpMessageComposer()); + NotificationUtilities.simpleAlert(LocalizeText('help.cfh.error.pending'), NotificationAlertType.MODERATION, null, null, LocalizeText('help.cfh.error.title')); + break; + case CallForHelpResult.HAS_ABUSIVE_CALL_CODE: + NotificationUtilities.simpleAlert(LocalizeText('help.cfh.error.abusive'), NotificationAlertType.MODERATION, null, null, LocalizeText('help.cfh.error.title')); + break; + default: + if(message.trim().length === 0) + { + message = LocalizeText('help.cfh.sent.text'); + } + NotificationUtilities.simpleAlert(message, NotificationAlertType.MODERATION, null, null, LocalizeText('help.cfh.sent.title')); + } }, []); CreateMessageHook(CallForHelpResultMessageEvent, onCallForHelpResultMessageEvent); + const onIssueCloseNotificationMessageEvent = useCallback((event: IssueCloseNotificationMessageEvent) => + { + const parser = event.getParser(); + + const message = LocalizeText('help.cfh.closed.' + GetCloseReasonKey(parser.closeReason)) + + NotificationUtilities.simpleAlert(message, NotificationAlertType.MODERATION, null, null, LocalizeText('mod.alert.title')); + }, []); + + CreateMessageHook(IssueCloseNotificationMessageEvent, onIssueCloseNotificationMessageEvent); + return null; } diff --git a/src/views/help/common/CallForHelpResult.ts b/src/views/help/common/CallForHelpResult.ts new file mode 100644 index 00000000..37e7ea1b --- /dev/null +++ b/src/views/help/common/CallForHelpResult.ts @@ -0,0 +1,5 @@ +export class CallForHelpResult +{ + public static readonly TOO_MANY_PENDING_CALLS_CODE = 1; + public static readonly HAS_ABUSIVE_CALL_CODE = 2; +} diff --git a/src/views/hotel-view/views/widgets/hall-of-fame-item/HallOfFameItemView.tsx b/src/views/hotel-view/views/widgets/hall-of-fame-item/HallOfFameItemView.tsx index 2aef86fe..4c6297b7 100644 --- a/src/views/hotel-view/views/widgets/hall-of-fame-item/HallOfFameItemView.tsx +++ b/src/views/hotel-view/views/widgets/hall-of-fame-item/HallOfFameItemView.tsx @@ -1,5 +1,5 @@ import { FC } from 'react'; -import { LocalizeText } from '../../../../../api'; +import { LocalizeFormattedNumber, LocalizeText } from '../../../../../api'; import { UserProfileIconView } from '../../../../../layout'; import { AvatarImageView } from '../../../../shared/avatar-image/AvatarImageView'; import { HallOfFameItemViewProps } from './HallOfFameItemView.types'; @@ -9,12 +9,12 @@ export const HallOfFameItemView: FC = props => const { data = null, level = 0 } = props; return ( -
+
-
-
{ level }. { data.userName }
-
{ LocalizeText('landing.view.competition.hof.points', [ 'points' ], [ data.currentScore.toString() ])}
+
+ { level }. { data.userName }
+
{ LocalizeText('landing.view.competition.hof.points', [ 'points' ], [ LocalizeFormattedNumber(data.currentScore).toString() ])}
diff --git a/src/views/hotel-view/views/widgets/hall-of-fame/HallOfFameWidgetView.scss b/src/views/hotel-view/views/widgets/hall-of-fame/HallOfFameWidgetView.scss index 74fa0189..63033a46 100644 --- a/src/views/hotel-view/views/widgets/hall-of-fame/HallOfFameWidgetView.scss +++ b/src/views/hotel-view/views/widgets/hall-of-fame/HallOfFameWidgetView.scss @@ -1,7 +1,7 @@ .hall-of-fame { background-color: rgba($black,.3); border-radius: $border-radius; - display: inline-block; + justify-content: center; .hof-user-container { display:inline-flex; @@ -16,22 +16,31 @@ .hof-tooltip { position: absolute; - display: inline; - z-index: 2; display: none; - background-color: #1c323f; + width: 125px; + max-width: 125px; + padding: 2px; + background-color: $gable-green; border: 2px solid rgba($white, 0.5); border-radius: $border-radius; - bottom:calc(100% - 20px); - left: 0; - right: 0; - margin: auto; - padding:2px; - color: #fff; + font-size: $font-size-sm; + z-index: $context-menu-zindex; + pointer-events: all; + left: -15px; + bottom: calc(100% - 10px); - .hof-tooltip-content { - padding:3px; - background-color: #3d5f6e; + .hof-header { + display: flex; + align-items: center; + justify-content: center; + gap: 5px; + background-color: $william; + color: $white; + min-width: 117px; + height: 25px; + max-height: 25px; + font-size: 16px; + margin-bottom: 2px; } &:after { diff --git a/src/views/hotel-view/views/widgets/hall-of-fame/HallOfFameWidgetView.tsx b/src/views/hotel-view/views/widgets/hall-of-fame/HallOfFameWidgetView.tsx index 5676a9be..2cbfdc8c 100644 --- a/src/views/hotel-view/views/widgets/hall-of-fame/HallOfFameWidgetView.tsx +++ b/src/views/hotel-view/views/widgets/hall-of-fame/HallOfFameWidgetView.tsx @@ -26,7 +26,7 @@ export const HallOfFameWidgetView: FC = props => if(!data) return null; return ( -
+
{ data.hof && (data.hof.length > 0) && data.hof.map((entry, index) => { return ; diff --git a/src/views/main/MainView.tsx b/src/views/main/MainView.tsx index 5cd09f84..ca00b8dc 100644 --- a/src/views/main/MainView.tsx +++ b/src/views/main/MainView.tsx @@ -6,6 +6,7 @@ import { TransitionAnimation, TransitionAnimationTypes } from '../../layout'; import { AchievementsView } from '../achievements/AchievementsView'; import { AvatarEditorView } from '../avatar-editor/AvatarEditorView'; import { CameraWidgetView } from '../camera/CameraWidgetView'; +import { CampaignView } from '../campaign/CampaignView'; import { CatalogView } from '../catalog/CatalogView'; import { ChatHistoryView } from '../chat-history/ChatHistoryView'; import { FloorplanEditorView } from '../floorplan-editor/FloorplanEditorView'; @@ -77,6 +78,7 @@ export const MainView: FC = props => +
); } diff --git a/src/views/mod-tools/ModToolsMessageHandler.tsx b/src/views/mod-tools/ModToolsMessageHandler.tsx index b810aa13..3357aef8 100644 --- a/src/views/mod-tools/ModToolsMessageHandler.tsx +++ b/src/views/mod-tools/ModToolsMessageHandler.tsx @@ -1,5 +1,6 @@ import { CfhSanctionMessageEvent, CfhTopicsInitEvent, IssueDeletedMessageEvent, IssueInfoMessageEvent, IssuePickFailedMessageEvent, ModeratorActionResultMessageEvent, ModeratorInitMessageEvent, ModeratorToolPreferencesEvent, RoomEngineEvent } from '@nitrots/nitro-renderer'; import { FC, useCallback } from 'react'; +import { MODTOOLS_NEW_TICKET, PlaySound } from '../../api/utils/PlaySound'; import { NotificationAlertEvent } from '../../events'; import { ModToolsEvent } from '../../events/mod-tools/ModToolsEvent'; import { ModToolsOpenRoomChatlogEvent } from '../../events/mod-tools/ModToolsOpenRoomChatlogEvent'; @@ -49,7 +50,7 @@ export const ModToolsMessageHandler: FC<{}> = props => const newTickets = tickets ? Array.from(tickets) : []; const existingIndex = newTickets.findIndex( entry => entry.issueId === parser.issueData.issueId) - + if(existingIndex > -1) { newTickets[existingIndex] = parser.issueData; @@ -57,6 +58,7 @@ export const ModToolsMessageHandler: FC<{}> = props => else { newTickets.push(parser.issueData); + PlaySound(MODTOOLS_NEW_TICKET); } dispatchModToolsState({ @@ -66,8 +68,6 @@ export const ModToolsMessageHandler: FC<{}> = props => } }); - //todo: play ticket sound - //GetNitroInstance().events.dispatchEvent(new NitroSoundEvent(NitroSoundEvent.PLAY_SOUND, sound) console.log(parser); }, [dispatchModToolsState, tickets]); diff --git a/src/views/purse/PurseMessageHandler.tsx b/src/views/purse/PurseMessageHandler.tsx index 5ff4558f..07ca8f09 100644 --- a/src/views/purse/PurseMessageHandler.tsx +++ b/src/views/purse/PurseMessageHandler.tsx @@ -1,5 +1,6 @@ import { ActivityPointNotificationMessageEvent, UserCreditsEvent, UserCurrencyEvent, UserSubscriptionEvent, UserSubscriptionParser } from '@nitrots/nitro-renderer'; import { FC, useCallback } from 'react'; +import { CREDITS, DUCKETS, PlaySound } from '../../api/utils/PlaySound'; import { CreateMessageHook } from '../../hooks/messages/message-event'; import { usePurseContext } from './context/PurseContext'; import { PurseMessageHandlerProps } from './PurseMessageHandler.types'; @@ -12,6 +13,8 @@ export const PurseMessageHandler: FC = props => { const parser = event.getParser(); + if(purse.credits && purse.credits !== parseFloat(parser.credits)) PlaySound(CREDITS); + purse.credits = parseFloat(parser.credits); purse.notify(); @@ -32,6 +35,8 @@ export const PurseMessageHandler: FC = props => purse.activityPoints.set(parser.type, parser.amount); + if(parser.type === 0) PlaySound(DUCKETS); + purse.notify(); }, [ purse ]); diff --git a/src/views/room/widgets/word-quiz/WordQuizWidgetView.tsx b/src/views/room/widgets/word-quiz/WordQuizWidgetView.tsx index af258d59..271853e4 100644 --- a/src/views/room/widgets/word-quiz/WordQuizWidgetView.tsx +++ b/src/views/room/widgets/word-quiz/WordQuizWidgetView.tsx @@ -106,9 +106,8 @@ export const WordQuizWidgetView: FC<{}> = props => { setUserAnswers(prev => { - const copy = new Map(prev); const keysToRemove: number[] = []; - copy.forEach((value, key) => + prev.forEach((value, key) => { value.secondsLeft--; @@ -118,8 +117,10 @@ export const WordQuizWidgetView: FC<{}> = props => } }); - keysToRemove.forEach(key => copy.delete(key)); + if(keysToRemove.length === 0) return prev; + const copy = new Map(prev); + keysToRemove.forEach(key => copy.delete(key)); return copy; }) diff --git a/src/views/toolbar/me/ToolbarMeView.scss b/src/views/toolbar/me/ToolbarMeView.scss index 44cd37bf..491ffad6 100644 --- a/src/views/toolbar/me/ToolbarMeView.scss +++ b/src/views/toolbar/me/ToolbarMeView.scss @@ -17,6 +17,7 @@ } .navigation-item { + position: relative; display: flex; align-items: center; justify-content: center; diff --git a/src/views/user-profile/views/relationships-container/RelationshipsContainerView.tsx b/src/views/user-profile/views/relationships-container/RelationshipsContainerView.tsx index e1bb648c..4f1745cb 100644 --- a/src/views/user-profile/views/relationships-container/RelationshipsContainerView.tsx +++ b/src/views/user-profile/views/relationships-container/RelationshipsContainerView.tsx @@ -25,7 +25,7 @@ export const RelationshipsContainerView: FC = p return (
- +
OnUserClick(relationshipInfo)}>