Skip to content

Commit 631f842

Browse files
authored
feature: matchmaking lobbies (#70)
1 parent fcdac85 commit 631f842

30 files changed

+8530
-1140
lines changed

components/PlayerDisplay.vue

Lines changed: 75 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,31 @@
11
<script lang="ts" setup>
22
import TimezoneFlag from "~/components/TimezoneFlag.vue";
3-
import { Ban, MicOff, MessageSquareOff } from "lucide-vue-next";
3+
import { Ban, MicOff, MessageSquareOff, UserPlus } from "lucide-vue-next";
44
</script>
55
<template>
66
<div
77
class="grid gap-2"
88
@click="viewPlayer"
99
:class="{
1010
'cursor-pointer': linkable,
11-
'grid-cols-[52px]': !showName && !showSteamId && !showFlag,
1211
'grid-cols-[52px_1fr]': showName || showSteamId || showFlag,
1312
}"
1413
>
1514
<div class="flex flex-col items-center justify-center relative">
16-
<Avatar shape="square">
17-
<AvatarImage
18-
:src="player.avatar_url"
19-
:alt="player.name"
20-
v-if="player.avatar_url"
21-
/>
22-
<AvatarFallback>
23-
{{ player.name.slice(0, 2) }}
24-
</AvatarFallback>
25-
</Avatar>
15+
<slot name="avatar">
16+
<Avatar shape="square">
17+
<AvatarImage
18+
:src="player.avatar_url"
19+
:alt="player.name"
20+
v-if="player?.avatar_url"
21+
/>
22+
<AvatarFallback>
23+
<slot name="avatar-fallback">
24+
{{ player?.name.slice(0, 2) }}
25+
</slot>
26+
</AvatarFallback>
27+
</Avatar>
28+
</slot>
2629
<slot name="status">
2730
<template v-if="isOnline && showOnline">
2831
<span
@@ -99,17 +102,31 @@ import { Ban, MicOff, MessageSquareOff } from "lucide-vue-next";
99102
</div>
100103
</div>
101104
<slot name="name-postfix"></slot>
105+
<TooltipProvider v-if="!isMe && showAddFriend && !isFriend">
106+
<Tooltip>
107+
<TooltipTrigger>
108+
<UserPlus
109+
class="w-4 h-4 cursor-pointer hover:text-primary"
110+
@click.stop="addAsFriend"
111+
/>
112+
</TooltipTrigger>
113+
<TooltipContent>Add as friend</TooltipContent>
114+
</Tooltip>
115+
</TooltipProvider>
102116
</div>
103117
<p class="text-muted-foreground" v-if="showSteamId">
104118
{{ player.steam_id }}
105119
</p>
106120
</div>
107121
</slot>
108122
</div>
123+
<slot name="footer"></slot>
109124
</div>
110125
</template>
111126

112127
<script lang="ts">
128+
import { typedGql } from "~/generated/zeus/typedDocumentNode";
129+
113130
export default {
114131
props: {
115132
size: {
@@ -118,7 +135,7 @@ export default {
118135
},
119136
player: {
120137
type: Object,
121-
required: true,
138+
required: false,
122139
},
123140
showName: {
124141
type: Boolean,
@@ -144,20 +161,63 @@ export default {
144161
type: Boolean,
145162
default: false,
146163
},
164+
showAddFriend: {
165+
type: Boolean,
166+
default: true,
167+
},
147168
},
148169
methods: {
170+
async addAsFriend() {
171+
await this.$apollo.mutate({
172+
mutation: typedGql("mutation")({
173+
insert_my_friends_one: [
174+
{
175+
object: {
176+
steam_id: this.player.steam_id,
177+
},
178+
},
179+
{
180+
steam_id: true,
181+
},
182+
],
183+
}),
184+
});
185+
},
149186
viewPlayer() {
150-
if (this.linkable) {
187+
if (this.linkable && this.player) {
151188
this.$router.push(`/players/${this.player.steam_id}`);
152189
}
153190
},
154191
},
155192
computed: {
193+
me() {
194+
return useAuthStore().me;
195+
},
196+
isMe() {
197+
if (!this.player) {
198+
return false;
199+
}
200+
201+
return this.me?.steam_id === this.player.steam_id;
202+
},
156203
isOnline() {
157-
return useMatchMakingStore().onlinePlayerSteamIds.includes(
204+
if (!this.player) {
205+
return false;
206+
}
207+
208+
return useMatchmakingStore().onlinePlayerSteamIds.includes(
158209
this.player.steam_id,
159210
);
160211
},
212+
isFriend() {
213+
if (!this.player) {
214+
return false;
215+
}
216+
217+
return useMatchmakingStore().friends.find((friend) => {
218+
return friend.steam_id == this.player.steam_id;
219+
});
220+
},
161221
},
162222
};
163223
</script>

components/PlayerSearch.vue

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,12 @@ export default {
104104
async searchPlayers(query?: string) {
105105
this.query = query || undefined;
106106
107+
const exclude = !this.canSelectSelf
108+
? this.exclude.concat(this.me.steam_id)
109+
: this.exclude;
110+
107111
if (this.onlineOnly) {
108-
this.players = useSearchStore().search(query, this.exclude);
112+
this.players = useSearchStore().search(query, exclude);
109113
return;
110114
}
111115
@@ -114,7 +118,7 @@ export default {
114118
body: {
115119
query,
116120
teamId: this.teamId,
117-
exclude: this.exclude,
121+
exclude: exclude,
118122
},
119123
});
120124

components/ServiceLogs.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,6 @@ export default {
211211
},
212212
unmounted() {
213213
this.logListener?.stop();
214-
// TODO - send to stop sending to my socket...
215214
},
216215
};
217216
</script>

components/match/MatchLobbyChat.vue renamed to components/chat/ChatLobby.vue

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
<script lang="ts" setup>
2+
import { Badge } from "~/components/ui/badge";
23
import { Button } from "~/components/ui/button";
34
import { Input } from "~/components/ui/input";
4-
import { FormControl, FormField, FormItem } from "~/components/ui/form";
55
import { CornerDownLeft } from "lucide-vue-next";
6-
import { Badge } from "~/components/ui/badge";
7-
import MatchLobbyChatMessage from "~/components/match/MatchLobbyChatMessage.vue";
8-
import { ref } from "vue";
6+
import ChatMessage from "~/components/chat/ChatMessage.vue";
7+
import { FormControl, FormField, FormItem } from "~/components/ui/form";
98
</script>
109

1110
<template>
@@ -14,17 +13,19 @@ import { ref } from "vue";
1413
>
1514
<div class="absolute right-3 top-3">
1615
<div class="flex">
17-
<Badge variant="secondary"> Lobby Chat </Badge>
16+
<Badge variant="secondary">
17+
<slot name="chat-label">Lobby Chat</slot>
18+
</Badge>
1819
</div>
1920
</div>
2021

2122
<div class="flex-1 overflow-y-auto max-h-screen" ref="chatMessages">
22-
<MatchLobbyChatMessage
23+
<ChatMessage
2324
:message="message"
2425
:previous-message="messages[index - 1]"
2526
v-for="(message, index) in messages"
2627
:key="index"
27-
></MatchLobbyChatMessage>
28+
></ChatMessage>
2829
</div>
2930

3031
<form
@@ -51,27 +52,34 @@ import { ref } from "vue";
5152
</div>
5253
</template>
5354
<script lang="ts">
54-
import { useForm } from "vee-validate";
55-
import { toTypedSchema } from "@vee-validate/zod";
5655
import * as z from "zod";
56+
import { useForm } from "vee-validate";
5757
import socket from "~/web-sockets/Socket";
58+
import { toTypedSchema } from "@vee-validate/zod";
59+
import type { Lobby } from "~/web-sockets/Socket";
5860
5961
export default {
6062
props: {
61-
matchId: {
63+
instance: {
64+
type: String,
6265
required: true,
66+
},
67+
lobbyId: {
6368
type: String,
69+
required: true,
6470
},
65-
messages: {
66-
default: () => [],
67-
type: Array as () => Array<Record<string, any>>,
71+
type: {
72+
type: String,
73+
required: true,
6874
},
6975
},
7076
data() {
7177
return {
78+
messages: [],
79+
lobby: undefined as Lobby | undefined,
7280
lobbyListener: undefined as { stop: () => void } | undefined,
7381
chatMessages: undefined as HTMLElement | undefined,
74-
isAtBottom: ref(true),
82+
isAtBottom: false,
7583
form: useForm({
7684
validationSchema: toTypedSchema(
7785
z.object({
@@ -82,12 +90,22 @@ export default {
8290
};
8391
},
8492
watch: {
85-
matchId: {
93+
lobbyId: {
8694
immediate: true,
8795
handler() {
96+
this.lobby?.leave();
97+
this.lobby = socket.joinLobby(
98+
this.instance,
99+
this.type as "match" | "team" | "matchmaking",
100+
this.lobbyId,
101+
);
102+
103+
this.updateLobbyMessages(this.lobby.messages);
104+
this.lobby.on("lobby:messages", this.updateLobbyMessages);
105+
88106
this.lobbyListener = socket.listenChat(
89-
"match",
90-
this.matchId,
107+
this.type,
108+
this.lobbyId,
91109
(message) => {
92110
this.messages.push(message);
93111
this.$nextTick(() => {
@@ -106,6 +124,11 @@ export default {
106124
},
107125
},
108126
methods: {
127+
updateLobbyMessages(messages: any) {
128+
this.messages = messages.sort((a, b) => {
129+
return a.timestamp - b.timestamp;
130+
});
131+
},
109132
checkIfAtBottom() {
110133
if (this.chatMessages) {
111134
const { scrollTop, scrollHeight, clientHeight } = this.chatMessages;
@@ -123,7 +146,11 @@ export default {
123146
return;
124147
}
125148
126-
socket.chat("match", this.matchId, message);
149+
socket.chat(
150+
this.type as "match" | "team" | "matchmaking",
151+
this.lobbyId,
152+
message,
153+
);
127154
128155
this.form.resetForm();
129156
this.$nextTick(() => {
@@ -138,6 +165,7 @@ export default {
138165
}
139166
},
140167
beforeUnmount() {
168+
this.lobby?.leave();
141169
this.lobbyListener?.stop();
142170
if (this.chatMessages) {
143171
this.chatMessages.removeEventListener("scroll", this.checkIfAtBottom);

components/match/MatchLobbyAccess.vue

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { Lock, Unlock } from "lucide-vue-next";
2+
import { Lock, Unlock, Send, Handshake } from "lucide-vue-next";
33
import { e_lobby_access_enum } from "~/generated/zeus";
44
import {
55
Popover,
@@ -42,6 +42,7 @@ import ClipBoard from "~/components/ClipBoard.vue";
4242
<Button
4343
v-for="access in [
4444
e_lobby_access_enum.Open,
45+
e_lobby_access_enum.Friends,
4546
e_lobby_access_enum.Invite,
4647
e_lobby_access_enum.Private,
4748
]"
@@ -53,7 +54,9 @@ import ClipBoard from "~/components/ClipBoard.vue";
5354
size="sm"
5455
:class="{
5556
'rounded-r-none': access === e_lobby_access_enum.Open,
56-
'rounded-none border-x-0': access === e_lobby_access_enum.Invite,
57+
'rounded-none border-x-0':
58+
access === e_lobby_access_enum.Invite ||
59+
access === e_lobby_access_enum.Friends,
5760
'rounded-l-none': access === e_lobby_access_enum.Private,
5861
}"
5962
>
@@ -118,12 +121,13 @@ export default {
118121
getIcon(access: e_lobby_access_enum) {
119122
switch (access) {
120123
case e_lobby_access_enum.Private:
121-
case e_lobby_access_enum.Invite:
122124
return Lock;
125+
case e_lobby_access_enum.Invite:
126+
return Send;
123127
case e_lobby_access_enum.Open:
124128
return Unlock;
125-
default:
126-
return Lock;
129+
case e_lobby_access_enum.Friends:
130+
return Handshake;
127131
}
128132
},
129133
},

components/match/MatchMaps.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import formatBits from "~/utilities/formatBits";
2828
>
2929
</div>
3030

31-
<!-- TODO - env variable url -->
3231
<div class="absolute top-3 right-3">
3332
<a
3433
target="_blank"

components/match/PlayerStatusDisplay.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ export default {
103103
return useMatchLobbyStore().lobbyChat[this.match.id];
104104
},
105105
isOnline() {
106-
return useMatchMakingStore().onlinePlayerSteamIds.includes(
106+
return useMatchmakingStore().onlinePlayerSteamIds.includes(
107107
this.member.player.steam_id,
108108
);
109109
},

0 commit comments

Comments
 (0)