Skip to content

Commit dc0b42c

Browse files
committed
[API] feat: add party join game functionality
1 parent e09df65 commit dc0b42c

4 files changed

Lines changed: 282 additions & 5 deletions

File tree

API/internal/rpc/party.go

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package rpc
33
import (
44
"context"
55
"encoding/json"
6+
"fmt"
67
"time"
78

89
"github.com/ArmchairDevelopers/Kyber/API/api/v1/pbapi"
@@ -13,6 +14,7 @@ import (
1314
"github.com/ArmchairDevelopers/Kyber/API/pkg/mq"
1415
"github.com/ArmchairDevelopers/Kyber/API/pkg/util"
1516
"github.com/ArmchairDevelopers/Kyber/API/pkg/ws"
17+
"go.mongodb.org/mongo-driver/bson"
1618
"go.uber.org/zap"
1719
"google.golang.org/grpc/codes"
1820
"google.golang.org/grpc/status"
@@ -88,6 +90,33 @@ func (s *PartyService) LeaveParty(ctx context.Context, _ *pbcommon.Empty) (*pbco
8890
},
8991
},
9092
})
93+
94+
party, err := s.store.Parties.GetByID(ctx, partyID)
95+
if err == nil && party != nil && party.JoinGameState != nil && party.LeaderID == user.ID {
96+
if err := s.store.Parties.Update(ctx, partyID, bson.M{"$unset": bson.M{"join_game_state": ""}}); err != nil {
97+
logger.L().Error("Failed to clear join game state", zap.Error(err))
98+
}
99+
s.partyPub.Publish(partyID, memberIDs, &pbapi.PartyEvent{
100+
Body: &pbapi.PartyEvent_JoinGameCancelled{
101+
JoinGameCancelled: &pbapi.JoinGameCancelledEvent{},
102+
},
103+
})
104+
}
105+
106+
if party.LeaderID == user.ID {
107+
newLeaderID := remainingSessions[0].UserID
108+
if err := s.store.Parties.Update(ctx, partyID, bson.M{"$set": bson.M{"leader_id": newLeaderID}}); err != nil {
109+
logger.L().Error("Failed to update party leader", zap.Error(err))
110+
}
111+
112+
s.partyPub.Publish(partyID, memberIDs, &pbapi.PartyEvent{
113+
Body: &pbapi.PartyEvent_NewLeader{
114+
NewLeader: &pbapi.NewLeaderEvent{
115+
NewLeaderId: newLeaderID,
116+
},
117+
},
118+
})
119+
}
91120
}
92121

93122
logger.L().Debug("User left party", zap.Uint64("party_id", partyID), zap.String("user_id", user.ID))
@@ -398,6 +427,116 @@ func (s *PartyService) GetParty(ctx context.Context, _ *pbcommon.Empty) (*pbapi.
398427
return &pbapi.GetPartyResponse{Party: party.Proto(sessions, mapped)}, nil
399428
}
400429

430+
func (s *PartyService) StartJoinGame(ctx context.Context, req *pbapi.StartJoinGameRequest) (*pbcommon.Empty, error) {
431+
user := ctx.Value("user").(*models.UserModel)
432+
433+
session, err := s.store.Sessions.GetByUserID(ctx, user.ID)
434+
if err != nil {
435+
logger.L().Error("Failed to get session", zap.Error(err))
436+
return nil, status.Error(codes.Internal, "Failed to get session")
437+
}
438+
439+
if session == nil || session.PartyID == nil {
440+
return nil, status.Error(codes.NotFound, "You are not in a party")
441+
}
442+
443+
party, err := s.store.Parties.GetByID(ctx, *session.PartyID)
444+
if err != nil {
445+
logger.L().Error("Failed to get party", zap.Error(err))
446+
return nil, status.Error(codes.Internal, "Failed to get party")
447+
}
448+
449+
if party == nil {
450+
return nil, status.Error(codes.NotFound, "Party not found")
451+
}
452+
453+
if party.LeaderID != user.ID {
454+
return nil, status.Error(codes.PermissionDenied, "Only the party leader can join a game")
455+
}
456+
457+
server, err := s.store.Servers.GetByID(ctx, req.GetServerId())
458+
if err != nil {
459+
logger.L().Error("Failed to get server", zap.Error(err))
460+
return nil, status.Error(codes.Internal, "Failed to get server")
461+
}
462+
463+
if server == nil {
464+
return nil, status.Error(codes.NotFound, "Server not found")
465+
}
466+
467+
punishment, err := s.store.Punishments.GetBanForServer(ctx, server.HostID, user.ID)
468+
if err != nil {
469+
logger.L().Error("Failed to check ban", zap.Error(err))
470+
return nil, status.Error(codes.Internal, "Failed to check ban")
471+
}
472+
473+
if punishment != nil {
474+
reason := fmt.Sprintf("You are banned from this server: %s", *punishment.Reason)
475+
return nil, status.Error(codes.PermissionDenied, reason)
476+
}
477+
478+
if server.Password != nil && *server.Password != req.Password {
479+
return nil, status.Error(codes.PermissionDenied, "Invalid password")
480+
}
481+
482+
sessions, err := s.store.Sessions.GetByPartyID(ctx, party.ID)
483+
if err != nil {
484+
logger.L().Error("Failed to get party sessions", zap.Error(err))
485+
return nil, status.Error(codes.Internal, "Failed to get party sessions")
486+
}
487+
488+
memberIDs := make([]string, len(sessions))
489+
for i, sess := range sessions {
490+
memberIDs[i] = sess.UserID
491+
}
492+
493+
if party.JoinGameState != nil {
494+
s.partyPub.Publish(party.ID, memberIDs, &pbapi.PartyEvent{
495+
Body: &pbapi.PartyEvent_JoinGameCancelled{
496+
JoinGameCancelled: &pbapi.JoinGameCancelledEvent{},
497+
},
498+
})
499+
}
500+
501+
mods := make([]models.ServerModModel, len(server.Mods))
502+
copy(mods, server.Mods)
503+
504+
joinGameState := &models.PartyJoinGameState{
505+
ServerID: server.ID,
506+
ServerName: server.Name,
507+
Mods: mods,
508+
// TODO: automatically share server passwords?
509+
}
510+
511+
if err := s.store.Parties.Update(ctx, party.ID, bson.M{"$set": bson.M{"join_game_state": joinGameState}}); err != nil {
512+
logger.L().Error("Failed to save join game state", zap.Error(err))
513+
return nil, status.Error(codes.Internal, "Failed to start join game")
514+
}
515+
516+
protoMods := make([]*pbcommon.ServerMod, len(server.Mods))
517+
for i, mod := range server.Mods {
518+
protoMods[i] = &pbcommon.ServerMod{
519+
Name: mod.Name,
520+
Version: mod.Version,
521+
Link: mod.Link,
522+
FileSize: mod.FileSize,
523+
}
524+
}
525+
526+
s.partyPub.Publish(party.ID, memberIDs, &pbapi.PartyEvent{
527+
Body: &pbapi.PartyEvent_JoinGame{
528+
JoinGame: &pbapi.JoinGameEvent{
529+
ServerId: server.ID,
530+
ServerName: server.Name,
531+
Mods: protoMods,
532+
LeaderId: user.ID,
533+
},
534+
},
535+
})
536+
537+
return &pbcommon.Empty{}, nil
538+
}
539+
401540
func (s *PartyService) createParty(ctx context.Context, user models.UserModel) (*models.PartyModel, error) {
402541
partyID, err := s.store.Parties.GetNextID(ctx)
403542
if err != nil {

API/pkg/models/party.go

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,20 @@ import (
44
"time"
55

66
"github.com/ArmchairDevelopers/Kyber/API/api/v1/pbapi"
7+
"github.com/ArmchairDevelopers/Kyber/API/api/v1/pbcommon"
78
)
89

10+
type PartyJoinGameState struct {
11+
ServerID string `json:"server_id" bson:"server_id"`
12+
ServerName string `json:"server_name" bson:"server_name"`
13+
Mods []ServerModModel `json:"mods" bson:"mods"`
14+
}
15+
916
type PartyModel struct {
10-
ID uint64 `json:"id" bson:"_id"`
11-
LeaderID string `json:"leader_id" bson:"leader_id"`
12-
CreatedAt time.Time `json:"created_at" bson:"created_at"`
17+
ID uint64 `json:"id" bson:"_id"`
18+
LeaderID string `json:"leader_id" bson:"leader_id"`
19+
CreatedAt time.Time `json:"created_at" bson:"created_at"`
20+
JoinGameState *PartyJoinGameState `json:"join_game_state,omitempty" bson:"join_game_state,omitempty"`
1321
}
1422

1523
func (p *PartyModel) Proto(sessions []SessionModel, users map[string]*UserModel) *pbapi.PartyState {
@@ -26,10 +34,30 @@ func (p *PartyModel) Proto(sessions []SessionModel, users map[string]*UserModel)
2634
})
2735
}
2836

29-
return &pbapi.PartyState{
37+
state := &pbapi.PartyState{
3038
Id: p.ID,
3139
LeaderId: p.LeaderID,
3240
Members: members,
3341
CreatedAt: p.CreatedAt.Unix(),
3442
}
43+
44+
if p.JoinGameState != nil {
45+
mods := make([]*pbcommon.ServerMod, len(p.JoinGameState.Mods))
46+
for i, mod := range p.JoinGameState.Mods {
47+
mods[i] = &pbcommon.ServerMod{
48+
Name: mod.Name,
49+
Version: mod.Version,
50+
Link: mod.Link,
51+
FileSize: mod.FileSize,
52+
}
53+
}
54+
55+
state.JoinGameState = &pbapi.JoinGameState{
56+
ServerId: p.JoinGameState.ServerID,
57+
ServerName: p.JoinGameState.ServerName,
58+
Mods: mods,
59+
}
60+
}
61+
62+
return state
3563
}

API/pkg/ws/session.go

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ func (s *SessionManager) HandleWS(w http.ResponseWriter, r *http.Request) {
243243
defer conn.Close()
244244

245245
for {
246-
_, _, err := conn.ReadMessage()
246+
_, msg, err := conn.ReadMessage()
247247
if err != nil {
248248
break
249249
}
@@ -254,6 +254,10 @@ func (s *SessionManager) HandleWS(w http.ResponseWriter, r *http.Request) {
254254
return
255255
}
256256

257+
if len(msg) > 0 {
258+
s.handleClientMessage(user.ID, msg)
259+
}
260+
257261
<-ticker.C
258262
}
259263

@@ -283,3 +287,55 @@ func (s *SessionManager) HandleWS(w http.ResponseWriter, r *http.Request) {
283287
}
284288
}
285289
}
290+
291+
func (s *SessionManager) handleClientMessage(userID string, msg []byte) {
292+
var clientEvent pbapi.SessionClientEvent
293+
if err := proto.Unmarshal(msg, &clientEvent); err != nil {
294+
return
295+
}
296+
297+
switch evt := clientEvent.Body.(type) {
298+
case *pbapi.SessionClientEvent_UpdateJoinGameStatus:
299+
s.handleJoinGameStatusUpdate(userID, evt.UpdateJoinGameStatus)
300+
}
301+
}
302+
303+
func (s *SessionManager) handleJoinGameStatusUpdate(userID string, evt *pbapi.UpdateJoinGameStatusEvent) {
304+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
305+
defer cancel()
306+
307+
// only temporarily
308+
// TODO: find a way to avoid hitting the database on every update, maybee by caching party memberships in memory?
309+
session, err := s.store.Sessions.GetByUserID(ctx, userID)
310+
if err != nil || session == nil || session.PartyID == nil {
311+
return
312+
}
313+
314+
partyID := *session.PartyID
315+
316+
party, err := s.store.Parties.GetByID(ctx, partyID)
317+
if err != nil || party == nil || party.JoinGameState == nil {
318+
return
319+
}
320+
321+
sessions, err := s.store.Sessions.GetByPartyID(ctx, partyID)
322+
if err != nil {
323+
logger.L().Error("Failed to get party sessions", zap.Error(err))
324+
return
325+
}
326+
327+
memberIDs := make([]string, len(sessions))
328+
for i, sess := range sessions {
329+
memberIDs[i] = sess.UserID
330+
}
331+
332+
s.partyPub.Publish(partyID, memberIDs, &pbapi.PartyEvent{
333+
Body: &pbapi.PartyEvent_JoinGameStatus{
334+
JoinGameStatus: &pbapi.JoinGameStatusEvent{
335+
UserId: userID,
336+
HasMods: evt.HasMods,
337+
ModDownloadPercentage: evt.ModDownloadPercentage,
338+
},
339+
},
340+
})
341+
}

Module/Proto/kyber_api.proto

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ service Party {
110110
rpc DeclineInvite(DeclineInviteRequest) returns (kyber_common.Empty);
111111

112112
rpc GetParty(kyber_common.Empty) returns (GetPartyResponse);
113+
114+
rpc StartJoinGame(StartJoinGameRequest) returns (kyber_common.Empty);
113115
}
114116

115117
// SessionService
@@ -123,6 +125,17 @@ message SessionEvent {
123125
}
124126
}
125127

128+
message SessionClientEvent {
129+
oneof body {
130+
UpdateJoinGameStatusEvent updateJoinGameStatus = 1;
131+
}
132+
}
133+
134+
message UpdateJoinGameStatusEvent {
135+
bool hasMods = 1;
136+
optional uint32 modDownloadPercentage = 2;
137+
}
138+
126139
// PartyService
127140

128141
message GetPartyResponse {
@@ -152,6 +165,20 @@ message PartyState {
152165
string leaderId = 2;
153166
repeated PartyMember members = 3;
154167
int64 createdAt = 4;
168+
optional JoinGameState joinGameState = 5;
169+
}
170+
171+
message JoinGameState {
172+
string serverId = 1;
173+
string serverName = 2;
174+
repeated kyber_common.ServerMod mods = 3;
175+
repeated JoinGameMemberStatus memberStatuses = 4;
176+
}
177+
178+
message JoinGameMemberStatus {
179+
string userId = 1;
180+
bool hasMods = 2;
181+
optional uint32 modDownloadPercentage = 3;
155182
}
156183

157184
message PartyMember {
@@ -167,9 +194,17 @@ message PartyEvent {
167194
MemberJoinedEvent memberJoined = 4;
168195
MemberLeftEvent memberLeft = 5;
169196
JoinGameEvent joinGame = 6;
197+
JoinGameStatusEvent joinGameStatus = 7;
198+
JoinGameReadyEvent joinGameReady = 8;
199+
JoinGameCancelledEvent joinGameCancelled = 9;
200+
NewLeaderEvent newLeader = 10;
170201
}
171202
}
172203

204+
message NewLeaderEvent {
205+
string newLeaderId = 1;
206+
}
207+
173208
message InviteReceivedEvent {
174209
uint64 partyId = 1;
175210
kyber_common.KyberPlayer inviter = 2;
@@ -196,6 +231,25 @@ message MemberLeftEvent {
196231
message JoinGameEvent {
197232
string serverId = 1;
198233
string serverName = 2;
234+
repeated kyber_common.ServerMod mods = 3;
235+
string leaderId = 4;
236+
}
237+
238+
message JoinGameStatusEvent {
239+
string userId = 1;
240+
bool hasMods = 2;
241+
optional uint32 modDownloadPercentage = 3;
242+
}
243+
244+
message JoinGameReadyEvent {
245+
string serverId = 1;
246+
}
247+
248+
message JoinGameCancelledEvent {}
249+
250+
message StartJoinGameRequest {
251+
string serverId = 1;
252+
string password = 2;
199253
}
200254

201255
// ReportService

0 commit comments

Comments
 (0)