diff --git a/client/package-lock.json b/client/package-lock.json index 6bb6e06c..c8c99b4e 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -11,6 +11,7 @@ "@kangc/v-md-editor": "^2.3.17", "axios": "^1.5.1", "babel-plugin-prismjs": "^2.1.0", + "dice-notation-js": "^1.0.3", "ef-infinite-canvas": "^0.6.6", "form-data": "^4.0.0", "jquery": "^3.7.1", @@ -5102,6 +5103,12 @@ "dev": true, "license": "MIT" }, + "node_modules/dice-notation-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dice-notation-js/-/dice-notation-js-1.0.3.tgz", + "integrity": "sha512-K02a/w3kjRp4QPYE3+qlXnQRVw9n00IIJHYtrxAryIIC112SP36YKo2Z9CR/f+ZKzj+YKJmCeTKnMIc4+crG5g==", + "license": "MIT" + }, "node_modules/dir-glob": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", diff --git a/client/package.json b/client/package.json index cae4980c..1d49981f 100644 --- a/client/package.json +++ b/client/package.json @@ -12,6 +12,7 @@ "@kangc/v-md-editor": "^2.3.17", "axios": "^1.5.1", "babel-plugin-prismjs": "^2.1.0", + "dice-notation-js": "^1.0.3", "ef-infinite-canvas": "^0.6.6", "form-data": "^4.0.0", "jquery": "^3.7.1", diff --git a/client/public/icons/game-icons/000000/delapouite/perspective-dice-six-svg.svg b/client/public/icons/game-icons/000000/delapouite/perspective-dice-six-svg.svg new file mode 100644 index 00000000..3efaf6a1 --- /dev/null +++ b/client/public/icons/game-icons/000000/delapouite/perspective-dice-six-svg.svg @@ -0,0 +1,7 @@ + + perspective-dice-six-svg + + + \ No newline at end of file diff --git a/client/public/img/d20.png b/client/public/img/d20.png new file mode 100644 index 00000000..b0fd421a Binary files /dev/null and b/client/public/img/d20.png differ diff --git a/client/public/img/dd-dice-512.webp b/client/public/img/dd-dice-512.webp new file mode 100644 index 00000000..62828192 Binary files /dev/null and b/client/public/img/dd-dice-512.webp differ diff --git a/client/src/assets/base.css b/client/src/assets/base.css index 3a4c1127..388ad274 100644 --- a/client/src/assets/base.css +++ b/client/src/assets/base.css @@ -22,7 +22,7 @@ --c-black-muter: #585858; - --c-black-blurred: #222222cc; + --c-black-blurred: #222222; --c-indigo: #2c3e50; @@ -96,6 +96,9 @@ --chat-background: var(--c-blacker); --text-disabled: #7e7e7e; + --color-chat-other: #1d1d1d; + --color-roll-dice-chat: #665750; + --color-hover: var() } } diff --git a/client/src/assets/main.css b/client/src/assets/main.css index 0c819d44..29c747f3 100644 --- a/client/src/assets/main.css +++ b/client/src/assets/main.css @@ -67,6 +67,13 @@ a { background-color: var(--c-button-green-hover); } +.btn-red { + background-color: var(--c-button-red); +} +.btn-red:hover { + background-color: var(--c-button-red-hover); +} + hr { border: 0; height: 1px; diff --git a/client/src/services/Dragonroll.js b/client/src/services/Dragonroll.js index b4118e1c..4ff24302 100644 --- a/client/src/services/Dragonroll.js +++ b/client/src/services/Dragonroll.js @@ -4,6 +4,7 @@ import { io } from "socket.io-client"; import Api from '@/services/Api' import { backendUrl } from './BackendURL'; import { GetUser } from './User'; +import { ExitGame } from './Game'; let emitter; @@ -25,7 +26,24 @@ let GetPlayerList = () => { return players; }; let GetCampaign = () => { return currentCampaign; }; let GetClient = () => { return currentPlayer; }; +let chatMessageId = 0; +const chat = ref([ + /* { + id: 1, + author: "66ae8aea3e78bb669e25010d", + chunks: [ + { + id: 1, + type: "text", + content: "Hola test" + } + ] + } */ +]); +let GetChatRef = () => chat; + socket.on('update-players', data => { + console.log(data); players.value = []; Object.keys(data).forEach((key) => { players.value.push(data[key]); @@ -33,22 +51,60 @@ socket.on('update-players', data => { }); }) -function DisplayCampaign(data){ +socket.on('message', (data) => { + // Add new chat message, ? + if(chat.value.length > 0) if(chat.value[chat.value.length - 1].author == data.author){ + chat.value[chat.value.length - 1].chunkSize += 1; + chat.value[chat.value.length - 1].chunks.push({ + id: chat.value[chat.value.length - 1].chunkSize, + type: data.type ? data.type : 'text', + content: data.content + }); + return; + } + + chatMessageId += 1; + chat.value.push({ + id: chatMessageId, + author: data.author, + chunkSize: 1, + chunks: [ + { + id: 1, + type: data.type ? data.type : 'text', + content: data.content + } + ] + }); +}); + +function SendMessage(data){ + socket.emit('message', data); +} + +function DisplayCampaign(data = currentCampaign){ ClearAll(); CreateWindow('campaign_preview', {campaign: data}); } function ConnectToCampaign(campaign){ currentCampaign = campaign; + chat.value = []; + socket.emit('enter', GetUser(), currentCampaign._id); } function Disconnect(){ socket.emit('exit'); + ExitGame(); + currentCampaign = null; + currentPlayer = null; + chat.value = []; } function GetPlayer(player_campaign){ + console.log(players.value); let index = players.value.findIndex((p) => {return p._id == player_campaign}); if(index != -1) return players.value[index]; } @@ -66,4 +122,7 @@ export { GetClient, GetPlayerList, GetPlayer, + + GetChatRef, + SendMessage }; \ No newline at end of file diff --git a/client/src/services/Windows.js b/client/src/services/Windows.js index 56369183..ab70af77 100644 --- a/client/src/services/Windows.js +++ b/client/src/services/Windows.js @@ -12,7 +12,9 @@ const windows = { campaign_list: ref([]), new_campaign: ref([]), join_campaign: ref([]), - campaign_preview: ref([]) + campaign_preview: ref([]), + chat: ref([]), + dice_menu: ref([]), }; const defValues = { @@ -66,6 +68,16 @@ const defValues = { ClearWindow('campaign_preview'); CreateWindow('campaign_list'); } + }, + 'chat': { + id: 'chat', + title: 'Chat', + close: true + }, + 'dice_menu': { + id: 'dice_menu', + title: 'Dice roll', + close: true } } diff --git a/client/src/views/managers/GameManager.vue b/client/src/views/managers/GameManager.vue index 15166845..98c86356 100644 --- a/client/src/views/managers/GameManager.vue +++ b/client/src/views/managers/GameManager.vue @@ -4,12 +4,26 @@ import { InGameRef } from '../../services/Game'; import IconButton from '../partials/game/IconButton.vue'; import { AddSound } from '../../services/Sound'; import TileMap from './TileMap.vue'; +import { DisplayCampaign, GetCampaign } from '../../services/Dragonroll'; +import { ClearAll, CreateWindow } from '../../services/Windows'; const game = ref(null); const in_game = InGameRef(); +function OpenCampaignPreview(){ + CreateWindow('campaign_preview', {campaign: GetCampaign(), style: 'compact', hide_start: true, back: undefined, close: true}); +} + +function OpenChat(){ + CreateWindow('chat'); +} + +function OpenDiceMenu(){ + CreateWindow('dice_menu'); +} + watch(game, () => { - if(game){ + if(game.value && in_game.value){ AddSound(game.value); } }); @@ -22,12 +36,14 @@ watch(game, () => {
- + - +
- + + +
@@ -44,5 +60,18 @@ watch(game, () => { display: flex; flex-direction: column; z-index: 1; + + user-select: none; +} + +.horizontal-button { + position: absolute; + top: 10px; + right: 10px; + display: flex; + flex-direction: row; + z-index: 1; + + user-select: none; } \ No newline at end of file diff --git a/client/src/views/managers/TileMap.vue b/client/src/views/managers/TileMap.vue index 5ff612f6..22604236 100644 --- a/client/src/views/managers/TileMap.vue +++ b/client/src/views/managers/TileMap.vue @@ -115,12 +115,16 @@ onMounted(() => { offsetY = oldOffsetY + ((event.clientY - startY) * (1 / scale)); draw(); - console.log("x: " + offsetX + ", y: " + offsetY); + // console.log("x: " + offsetX + ", y: " + offsetY); }); tilemap.addEventListener("mouseup", () => mouseDown = false); addEventListener("resize", draw) + + offsetX = window.innerWidth / 2; + offsetY = window.innerHeight / 2; + draw(); }); diff --git a/client/src/views/managers/WindowManager.vue b/client/src/views/managers/WindowManager.vue index 5896dc50..2b7ac576 100644 --- a/client/src/views/managers/WindowManager.vue +++ b/client/src/views/managers/WindowManager.vue @@ -15,6 +15,8 @@ import CampaignListWindow from '../windows/campaigns/CampaignListWindow.vue' import NewCampaignWindow from '../windows/campaigns/NewCampaignWindow.vue' import JoinCampaignWindow from '../windows/campaigns/JoinCampaignWindow.vue' import CampaignPreviewWindow from '@/views/windows/campaigns/CampaignPreviewWindow.vue' +import ChatWindow from '../windows/game/ChatWindow.vue' +import DiceWindow from '../windows/game/DiceWindow.vue' // Gestionem ventanas const reload = ReloadRef(); @@ -32,6 +34,8 @@ const campaign_list = windows.campaign_list; const new_campaign = windows.new_campaign; const join_campaign = windows.join_campaign; const campaign_preview = windows.campaign_preview; +const chat = windows.chat; +const dice_menu = windows.dice_menu; @@ -48,6 +52,8 @@ const campaign_preview = windows.campaign_preview; + + @@ -57,7 +63,7 @@ const campaign_preview = windows.campaign_preview; .window-wrapper { background-color: var(--window-background); - backdrop-filter: blur(10px); + /* backdrop-filter: blur(10px); */ position: fixed; diff --git a/client/src/views/partials/ChatComponent.vue b/client/src/views/partials/ChatComponent.vue new file mode 100644 index 00000000..38b84b08 --- /dev/null +++ b/client/src/views/partials/ChatComponent.vue @@ -0,0 +1,146 @@ + + + + + diff --git a/client/src/views/partials/MessageChunk.vue b/client/src/views/partials/MessageChunk.vue new file mode 100644 index 00000000..3ee32aab --- /dev/null +++ b/client/src/views/partials/MessageChunk.vue @@ -0,0 +1,61 @@ + + + + + \ No newline at end of file diff --git a/client/src/views/partials/MessageComponent.vue b/client/src/views/partials/MessageComponent.vue new file mode 100644 index 00000000..ca6705f7 --- /dev/null +++ b/client/src/views/partials/MessageComponent.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/client/src/views/partials/game/IconButton.vue b/client/src/views/partials/game/IconButton.vue index e4f34d78..5f88a9a4 100644 --- a/client/src/views/partials/game/IconButton.vue +++ b/client/src/views/partials/game/IconButton.vue @@ -1,13 +1,16 @@ @@ -16,6 +19,12 @@ let icon = props.icon; .icon-button { height: 32px; width: 32px; + + &.big { + height: 42px; + width: 42px; + } + background-color: var(--color-background-soft); border-radius: 6px; display: flex; @@ -34,4 +43,9 @@ let icon = props.icon; height: 24px; width: 24px; } + +.big { + height: 38px; + width: 38px; +} \ No newline at end of file diff --git a/client/src/views/windows/campaigns/CampaignPreviewWindow.vue b/client/src/views/windows/campaigns/CampaignPreviewWindow.vue index fe7aff9b..fd3b3159 100644 --- a/client/src/views/windows/campaigns/CampaignPreviewWindow.vue +++ b/client/src/views/windows/campaigns/CampaignPreviewWindow.vue @@ -4,23 +4,39 @@ import { SetupHandle, SetSize, SetPosition, ResetPosition } from '@/services/Win import WindowHandle from '@/views/partials/WindowHandle.vue'; import PlayerList from '../../partials/PlayerList.vue'; -import { DisplayToast, GetCampaign, GetClient } from '../../../services/Dragonroll'; +import { Disconnect, DisplayToast, GetCampaign, GetClient } from '../../../services/Dragonroll'; import CampaignBookList from '../../partials/books/CampaignBookList.vue'; -import { ClearWindow } from '../../../services/Windows'; +import { ClearAll, ClearWindow, CreateWindow } from '../../../services/Windows'; import { LaunchGame } from '../../../services/Game'; +import { AddSound } from '../../../services/Sound'; +import ChatComponent from '../../partials/ChatComponent.vue'; const handle = ref(null); const props = defineProps(['data']); const data = props.data; +const hide_start = ref(false); +const hide_chat = ref(false); + +const container = ref(null); + let id = data.id; onMounted(() => { SetupHandle(id, handle); - SetSize(id, {x: 1200, y: 750}); + + if(data.style == 'compact') { + SetSize(id, {x: 800, y: 750}); + hide_chat.value = true; + } else { + SetSize(id, {x: 1200, y: 750}); + } + ResetPosition(id, "center"); - console.log(data); + hide_start.value = data.hide_start; + + AddSound(container.value) }); function CopyCode(){ @@ -33,6 +49,12 @@ function Launch(){ LaunchGame(); } +function Exit(){ + Disconnect(); + ClearAll(); + CreateWindow('campaign_list'); +} + @@ -43,7 +65,7 @@ function Launch(){ -
+

Players

@@ -61,11 +83,12 @@ function Launch(){
- + +
-
-

Chat

+
+
@@ -115,6 +138,10 @@ function Launch(){ display: grid; grid-template-columns: 3fr 5fr 4fr; + + &.campaign-preview-compact { + grid-template-columns: 2fr 3fr; + } } .campaign-preview-column { diff --git a/client/src/views/windows/game/ChatWindow.vue b/client/src/views/windows/game/ChatWindow.vue new file mode 100644 index 00000000..2e2e3021 --- /dev/null +++ b/client/src/views/windows/game/ChatWindow.vue @@ -0,0 +1,38 @@ + + + + + + + diff --git a/client/src/views/windows/game/DiceWindow.vue b/client/src/views/windows/game/DiceWindow.vue new file mode 100644 index 00000000..2a3fa56c --- /dev/null +++ b/client/src/views/windows/game/DiceWindow.vue @@ -0,0 +1,140 @@ + + + + + + + diff --git a/server/io/campaign.js b/server/io/campaign.js index 55da8ec6..d490f68e 100644 --- a/server/io/campaign.js +++ b/server/io/campaign.js @@ -33,7 +33,8 @@ module.exports = io => { socket.campaign = campaignId; if(!sessions[campaignId]) sessions[campaignId] = { - players: await GetOfflinePlayers(campaignId) + players: await GetOfflinePlayers(campaignId), + chat: [] }; @@ -55,5 +56,9 @@ module.exports = io => { console.log(socket.user.username + " ha salido!") }); + + socket.on('message', (data) => { + io.to(socket.campaign).emit('message', data); + }) }); } \ No newline at end of file