Coverage Report
Generated on 17 Jan 16 05:58 +0000 with gocov-html
Package Overview: github.com/TF2Stadium/Helen/controllers/socket/internal/handler 31.76%

This is a coverage report created after analysis of the github.com/TF2Stadium/Helen/controllers/socket/internal/handler package. It has been generated with the following command:

gocov test github.com/TF2Stadium/Helen/controllers/socket/internal/handler | gocov-html

Here are the stats. Please select a function name to view its implementation and see what's left for testing.

InitializeBans(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/ban.go100.00%4/4
Global.GetSocketInfo(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/global.go100.00%2/2
Lobby.Name(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go100.00%1/1
Admin.Name(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/admin.go100.00%1/1
Global.Name(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/global.go100.00%1/1
Chat.Name(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/chat.go100.00%1/1
Player.Name(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/player.go100.00%1/1
newRequirement(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go83.33%5/6
Lobby.LobbyClose(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go76.47%13/17
Lobby.LobbyCreate(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go73.08%38/52
Chat.ChatSend(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/chat.go68.75%22/32
Lobby.LobbyLeave(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go66.67%6/9
Lobby.LobbySpectatorLeave(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go64.71%11/17
Lobby.LobbySpectatorJoin(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go64.00%16/25
Lobby.LobbyJoin(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go52.63%20/38
removePlayerFromLobby(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go40.00%6/15
Admin.AdminChangeRole(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/admin.go0.00%0/22
Player.PlayerReady(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/player.go0.00%0/21
Player.PlayerNotReady(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/player.go0.00%0/18
Lobby.LobbyServerReset(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go0.00%0/16
Lobby.ServerVerify(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go0.00%0/16
Player.PlayerSettingsSet(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/player.go0.00%0/16
Player.PlayerSettingsGet(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/player.go0.00%0/15
Lobby.LobbyBan(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go0.00%0/15
newBan(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/ban.go0.00%0/14
Lobby.LobbyKick(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go0.00%0/14
Player.PlayerProfile(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/player.go0.00%0/12
@64:28(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/ban.go0.00%0/11
@90:33(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/ban.go0.00%0/10
playerCanKick(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go0.00%0/9
removeUnreadyPlayers(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go0.00%0/8
Global.GetConstant(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/global.go0.00%0/8
unban(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/ban.go0.00%0/7
@351:34(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go0.00%0/6
Lobby.RequestLobbyListData(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go0.00%0/4
FakeResponseWriter.Write(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/admin.go0.00%0/1
FakeResponseWriter.Header(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/admin.go0.00%0/1
FakeResponseWriter.WriteHeader(...)github.com/TF2Stadium/Helen/controllers/socket/internal/handler/admin.go0.00%0/0
github.com/TF2Stadium/Helen/controllers/socket/internal/handler31.76%148/466
func InitializeBans
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/ban.go:

52
func InitializeBans(server *wsevent.Server) {
53
        bans := []struct {
54
                eventName string
55
                action    authority.AuthAction
56
                banType   models.PlayerBanType
57
        }{
58
                {"banJoin", helpers.ActionBanJoin, models.PlayerBanJoin},
59
                {"banCreate", helpers.ActionBanCreate, models.PlayerBanCreate},
60
                {"banChat", helpers.ActionBanChat, models.PlayerBanChat},
61
        }
62
63
        for _, ban := range bans {
64
                server.On(ban.eventName, func(_ *wsevent.Server, so *wsevent.Client, data []byte) interface{} {
65
                        reqerr := chelpers.CheckPrivilege(so, ban.action)
66
                        if reqerr != nil {
67
                                return reqerr
68
                        }
69
70
                        var args struct {
71
                                SteamID *string `json:"steamid"`
72
                                Until   *int64  `json:"until"`
73
                                Reason  *string `json:"reason"`
74
                        }
75
76
                        if err := chelpers.GetParams(data, &args); err != nil {
77
                                return helpers.NewTPErrorFromError(err)
78
                        }
79
80
                        steamID := chelpers.GetSteamId(so.Id())
81
82
                        tperr := newBan(*args.SteamID, steamID, ban.action, ban.banType, *args.Until, *args.Reason)
83
                        if tperr != nil {
84
                                return tperr
85
                        }
86
87
                        return chelpers.EmptySuccessJS
88
                })
89
90
                server.On("Un"+ban.eventName, func(_ *wsevent.Server, so *wsevent.Client, data []byte) interface{} {
91
                        reqerr := chelpers.CheckPrivilege(so, ban.action)
92
                        if reqerr != nil {
93
                                return reqerr
94
                        }
95
96
                        var args struct {
97
                                SteamID *string `json:"steamid"`
98
                        }
99
100
                        if err := chelpers.GetParams(data, &args); err != nil {
101
                                return helpers.NewTPErrorFromError(err)
102
                        }
103
104
                        err := unban(*args.SteamID, ban.banType)
105
                        if err != nil {
106
                                return err
107
                        }
108
                        return chelpers.EmptySuccessJS
109
                })
110
        }
111
}
func Global.GetSocketInfo
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/global.go:

40
func (Global) GetSocketInfo(server *wsevent.Server, so *wsevent.Client, data []byte) interface{} {
41
        socketinfo := struct {
42
                ID    string   `json:"id"`
43
                Rooms []string `json:"rooms"`
44
        }{so.Id(), server.RoomsJoined(so.Id())}
45
46
        return chelpers.NewResponse(socketinfo)
47
}
func Lobby.Name
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go:

26
func (Lobby) Name(s string) string {
27
        return string((s[0])+32) + s[1:]
28
}
func Admin.Name
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/admin.go:

29
func (Admin) Name(s string) string {
30
        return string((s[0])+32) + s[1:]
31
}
func Global.Name
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/global.go:

17
func (Global) Name(s string) string {
18
        return string((s[0])+32) + s[1:]
19
}
func Chat.Name
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/chat.go:

22
func (Chat) Name(s string) string {
23
        return string((s[0])+32) + s[1:]
24
}
func Player.Name
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/player.go:

13
func (Player) Name(s string) string {
14
        return string((s[0])+32) + s[1:]
15
}
func newRequirement
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go:

42
func newRequirement(team, class string, requirement Requirement, lobby *models.Lobby) *helpers.TPError {
43
        slot, err := models.LobbyGetPlayerSlot(lobby.Type, team, class)
44
        if err != nil {
45
                return err
46
        }
47
        slotReq := &models.Requirement{
48
                LobbyID: lobby.ID,
49
                Slot:    slot,
50
                Hours:   requirement.Hours,
51
                Lobbies: requirement.Lobbies,
52
        }
53
        slotReq.Save()
54
55
        return nil
56
}
func Lobby.LobbyClose
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go:

253
func (Lobby) LobbyClose(server *wsevent.Server, so *wsevent.Client, data []byte) interface{} {
254
        var args struct {
255
                Id *uint `json:"id"`
256
        }
257
258
        if err := chelpers.GetParams(data, &args); err != nil {
259
                return helpers.NewTPErrorFromError(err)
260
261
        }
262
263
        player, _ := models.GetPlayerBySteamID(chelpers.GetSteamId(so.Id()))
264
265
        lob, tperr := models.GetLobbyByIDServer(uint(*args.Id))
266
        if tperr != nil {
267
                return tperr
268
        }
269
270
        if player.SteamID != lob.CreatedBySteamID && player.Role != helpers.RoleAdmin {
271
                return helpers.NewTPError("Player not authorized to close lobby.", -1)
272
273
        }
274
275
        if lob.State == models.LobbyStateEnded {
276
                return helpers.NewTPError("Lobby already closed.", -1)
277
        }
278
279
        models.FumbleLobbyEnded(lob)
280
281
        lob.Close(true)
282
        models.BroadcastLobbyList() // has to be done manually for now
283
284
        notify := fmt.Sprintf("Lobby closed by %s", player.Name)
285
        models.SendNotification(notify, int(lob.ID))
286
287
        return chelpers.EmptySuccessJS
288
}
func Lobby.LobbyCreate
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go:

58
func (Lobby) LobbyCreate(_ *wsevent.Server, so *wsevent.Client, data []byte) interface{} {
59
        player, _ := models.GetPlayerBySteamID(chelpers.GetSteamId(so.Id()))
60
        if banned, until := player.IsBannedWithTime(models.PlayerBanCreate); banned {
61
                str := fmt.Sprintf("You've been banned from creating lobbies till %s", until.Format(time.RFC822))
62
                return helpers.NewTPError(str, -1)
63
        }
64
65
        var args struct {
66
                Map         *string `json:"map"`
67
                Type        *string `json:"type" valid:"debug,6s,highlander,4v4,ultiduo,bball"`
68
                League      *string `json:"league" valid:"ugc,etf2l,esea,asiafortress,ozfortress"`
69
                Server      *string `json:"server"`
70
                RconPwd     *string `json:"rconpwd"`
71
                WhitelistID *string `json:"whitelistID"`
72
                Mumble      *bool   `json:"mumbleRequired"`
73
74
                Password            *string `json:"password" empty:"-"`
75
                SteamGroupWhitelist *string `json:"steamGroupWhitelist" empty:"-"`
76
77
                Requirements *struct {
78
                        Classes map[string]Requirement `json:"classes,omitempty"`
79
                        General Requirement            `json:"general,omitempty"`
80
                } `json:"requirements" empty:"-"`
81
        }
82
83
        err := chelpers.GetParams(data, &args)
84
        if err != nil {
85
                return helpers.NewTPErrorFromError(err)
86
        }
87
88
        var steamGroup string
89
90
        if *args.SteamGroupWhitelist != "" && !rSteamGroup.MatchString(*args.SteamGroupWhitelist) {
91
                return helpers.NewTPError("Invalid Steam group URL", -1)
92
        } else if rSteamGroup.MatchString(*args.SteamGroupWhitelist) {
93
                steamGroup = rSteamGroup.FindStringSubmatch(*args.SteamGroupWhitelist)[1]
94
        }
95
96
        var playermap = map[string]models.LobbyType{
97
                "debug":      models.LobbyTypeDebug,
98
                "6s":         models.LobbyTypeSixes,
99
                "highlander": models.LobbyTypeHighlander,
100
                "ultiduo":    models.LobbyTypeUltiduo,
101
                "bball":      models.LobbyTypeBball,
102
                "4v4":        models.LobbyTypeFours,
103
        }
104
105
        lobbyType := playermap[*args.Type]
106
107
        var count int
108
        db.DB.Table("server_records").Where("host = ?", *args.Server).Count(&count)
109
        if count != 0 {
110
                return helpers.NewTPError("A lobby is already using this server.", -1)
111
        }
112
113
        randBytes := make([]byte, 6)
114
        rand.Read(randBytes)
115
        serverPwd := base64.URLEncoding.EncodeToString(randBytes)
116
117
        //TODO what if playermap[lobbytype] is nil?
118
        info := models.ServerRecord{
119
                Host:           *args.Server,
120
                RconPassword:   *args.RconPwd,
121
                ServerPassword: serverPwd}
122
        // err = models.VerifyInfo(info)
123
        // if err != nil {
124
        //         bytes, _ := helpers.NewTPErrorFromError(err).Encode()
125
        //         return string(bytes)
126
        // }
127
128
        lob := models.NewLobby(*args.Map, lobbyType, *args.League, info, *args.WhitelistID, *args.Mumble, steamGroup, *args.Password)
129
        lob.CreatedBySteamID = player.SteamID
130
        lob.RegionCode, lob.RegionName = chelpers.GetRegion(*args.Server)
131
        if (lob.RegionCode == "" || lob.RegionName == "") && config.Constants.GeoIP != "" {
132
                return helpers.NewTPError("Couldn't find region server.", 1)
133
        }
134
        lob.Save()
135
136
        err = lob.SetupServer()
137
        if err != nil { //lobby setup failed, delete lobby and corresponding server record
138
                qerr := db.DB.Where("id = ?", lob.ID).Delete(&models.Lobby{}).Error
139
                if qerr != nil {
140
                        helpers.Logger.Warning(qerr.Error())
141
                }
142
                db.DB.Delete(&lob.ServerInfo)
143
                return helpers.NewTPErrorFromError(err)
144
        }
145
146
        lob.State = models.LobbyStateWaiting
147
        lob.Save()
148
149
        models.FumbleLobbyCreated(lob)
150
151
        if args.Requirements != nil {
152
                for class, requirement := range (*args.Requirements).Classes {
153
                        if requirement.Restricted.Blu {
154
                                err := newRequirement("blu", class, requirement, lob)
155
                                if err != nil {
156
                                        return err
157
                                }
158
                        }
159
                        if requirement.Restricted.Red {
160
                                err := newRequirement("red", class, requirement, lob)
161
                                if err != nil {
162
                                        return err
163
                                }
164
                        }
165
                }
166
                general := &models.Requirement{
167
                        LobbyID: lob.ID,
168
                        Hours:   args.Requirements.General.Hours,
169
                        Lobbies: args.Requirements.General.Lobbies,
170
                        Slot:    -1,
171
                }
172
                general.Save()
173
        }
174
        return chelpers.NewResponse(
175
                struct {
176
                        ID uint `json:"id"`
177
                }{lob.ID})
178
}
func Chat.ChatSend
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/chat.go:

28
func (Chat) ChatSend(server *wsevent.Server, so *wsevent.Client, data []byte) interface{} {
29
        steamid := chelpers.GetSteamId(so.Id())
30
        now := time.Now().Unix()
31
        if now-lastChatTime[steamid] == 0 {
32
                return helpers.NewTPError("You're sending messages too quickly", -1)
33
        }
34
35
        player, tperr := models.GetPlayerBySteamID(steamid)
36
37
        var args struct {
38
                Message *string `json:"message"`
39
                Room    *int    `json:"room"`
40
        }
41
42
        err := chelpers.GetParams(data, &args)
43
        if err != nil {
44
                return helpers.NewTPErrorFromError(err)
45
        }
46
47
        lastChatTime[steamid] = now
48
        if tperr != nil {
49
                return tperr
50
        }
51
52
        //helpers.Logger.Debug("received chat message: %s %s", *args.Message, player.Name)
53
54
        if *args.Room > 0 {
55
                var count int
56
                spec := player.IsSpectatingID(uint(*args.Room))
57
                //Check if player has either joined, or is spectating lobby
58
                db.DB.Table("lobby_slots").Where("lobby_id = ? AND player_id = ?", *args.Room, player.ID).Count(&count)
59
60
                if !spec && count == 0 {
61
                        return helpers.NewTPError("Player is not in the lobby.", 5)
62
                }
63
        } else {
64
                // else room is the lobby list room
65
                *args.Room, _ = strconv.Atoi(config.Constants.GlobalChatRoom)
66
        }
67
        switch {
68
        case len(*args.Message) == 0:
69
                return helpers.NewTPError("Cannot send an empty message", 4)
70
71
        case (*args.Message)[0] == '\n':
72
                return helpers.NewTPError("Cannot send messages prefixed with newline", 4)
73
74
        case len(*args.Message) > 120:
75
                return helpers.NewTPError("Message too long", 4)
76
        }
77
78
        message := models.NewChatMessage(*args.Message, *args.Room, player)
79
        if tperr != nil {
80
                return tperr
81
        }
82
83
        message.Save()
84
85
        if strings.HasPrefix(*args.Message, "!admin") {
86
                chelpers.SendToSlack(*args.Message, player.Name, player.SteamID)
87
                return chelpers.EmptySuccessJS
88
        }
89
90
        message.Send()
91
92
        return chelpers.EmptySuccessJS
93
}
func Lobby.LobbyLeave
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go:

565
func (Lobby) LobbyLeave(server *wsevent.Server, so *wsevent.Client, data []byte) interface{} {
566
        var args struct {
567
                Id *uint `json:"id"`
568
        }
569
        if err := chelpers.GetParams(data, &args); err != nil {
570
                return helpers.NewTPErrorFromError(err)
571
        }
572
573
        steamId := chelpers.GetSteamId(so.Id())
574
575
        lob, player, tperr := removePlayerFromLobby(*args.Id, steamId)
576
        if tperr != nil {
577
                return tperr
578
        }
579
580
        hooks.AfterLobbyLeave(server, lob, player)
581
582
        return chelpers.EmptySuccessJS
583
}
func Lobby.LobbySpectatorLeave
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go:

585
func (Lobby) LobbySpectatorLeave(server *wsevent.Server, so *wsevent.Client, data []byte) interface{} {
586
        var args struct {
587
                Id *uint `json:"id"`
588
        }
589
        if err := chelpers.GetParams(data, &args); err != nil {
590
                return helpers.NewTPErrorFromError(err)
591
        }
592
593
        steamId := chelpers.GetSteamId(so.Id())
594
        player, tperr := models.GetPlayerBySteamID(steamId)
595
        if tperr != nil {
596
                return tperr
597
        }
598
599
        lob, tperr := models.GetLobbyByID(*args.Id)
600
        if tperr != nil {
601
                return tperr
602
        }
603
604
        if !player.IsSpectatingID(lob.ID) {
605
                if id, _ := player.GetLobbyID(false); id == *args.Id {
606
                        hooks.AfterLobbySpecLeave(server, so, lob)
607
                        return chelpers.EmptySuccessJS
608
                }
609
        }
610
611
        lob.RemoveSpectator(player, true)
612
        hooks.AfterLobbySpecLeave(server, so, lob)
613
614
        return chelpers.EmptySuccessJS
615
}
func Lobby.LobbySpectatorJoin
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go:

399
func (Lobby) LobbySpectatorJoin(server *wsevent.Server, so *wsevent.Client, data []byte) interface{} {
400
        var args struct {
401
                Id *uint `json:"id"`
402
        }
403
404
        if err := chelpers.GetParams(data, &args); err != nil {
405
                return helpers.NewTPErrorFromError(err)
406
        }
407
408
        var lob *models.Lobby
409
        lob, tperr := models.GetLobbyByID(*args.Id)
410
411
        if tperr != nil {
412
                return tperr
413
        }
414
415
        player, tperr := models.GetPlayerBySteamID(chelpers.GetSteamId(so.Id()))
416
        if tperr != nil {
417
                return tperr
418
        }
419
420
        var specSameLobby bool
421
422
        arr, tperr := player.GetSpectatingIds()
423
        if len(arr) != 0 {
424
                for _, id := range arr {
425
                        if id == *args.Id {
426
                                specSameLobby = true
427
                                continue
428
                        }
429
                        //a socket should only spectate one lobby, remove socket from
430
                        //any other lobby room
431
                        //multiple sockets from one player can spectatte multiple lobbies
432
                        server.RemoveClient(so.Id(), fmt.Sprintf("%d_public", id))
433
                }
434
        }
435
436
        // If the player is already in the lobby (either joined a slot or is spectating), don't add them.
437
        // Just Broadcast the lobby to them, so the frontend displays it.
438
        if id, _ := player.GetLobbyID(false); id != *args.Id && !specSameLobby {
439
                tperr = lob.AddSpectator(player)
440
441
                if tperr != nil {
442
                        return tperr
443
                }
444
        }
445
446
        hooks.AfterLobbySpec(server, so, lob)
447
        models.BroadcastLobbyToUser(lob, player.SteamID)
448
        return chelpers.EmptySuccessJS
449
}
func Lobby.LobbyJoin
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go:

290
func (Lobby) LobbyJoin(server *wsevent.Server, so *wsevent.Client, data []byte) interface{} {
291
        player, _ := models.GetPlayerBySteamID(chelpers.GetSteamId(so.Id()))
292
        if banned, until := player.IsBannedWithTime(models.PlayerBanJoin); banned {
293
                str := fmt.Sprintf("You have been banned from joining lobbies till %s", until.Format(time.RFC822))
294
                return helpers.NewTPError(str, -1)
295
        }
296
297
        var args struct {
298
                Id       *uint   `json:"id"`
299
                Class    *string `json:"class"`
300
                Team     *string `json:"team" valid:"red,blu"`
301
                Password *string `json:"password" empty:"-"`
302
        }
303
304
        if err := chelpers.GetParams(data, &args); err != nil {
305
                return helpers.NewTPErrorFromError(err)
306
        }
307
        //helpers.Logger.Debug("id %d class %s team %s", *args.Id, *args.Class, *args.Team)
308
309
        lob, tperr := models.GetLobbyByID(*args.Id)
310
        if tperr != nil {
311
                return tperr
312
        }
313
314
        if lob.State == models.LobbyStateEnded {
315
                return helpers.NewTPError("Cannot join a closed lobby.", -1)
316
317
        }
318
319
        //Check if player is in the same lobby
320
        var sameLobby bool
321
        if id, err := player.GetLobbyID(false); err == nil && id == *args.Id {
322
                sameLobby = true
323
        }
324
325
        slot, tperr := models.LobbyGetPlayerSlot(lob.Type, *args.Team, *args.Class)
326
        if tperr != nil {
327
                return tperr
328
        }
329
330
        if prevId, _ := player.GetLobbyID(false); prevId != 0 && !sameLobby {
331
                server.RemoveClient(so.Id(), fmt.Sprintf("%d_public", prevId))
332
                server.RemoveClient(so.Id(), fmt.Sprintf("%d_private", prevId))
333
        }
334
335
        tperr = lob.AddPlayer(player, slot, *args.Password)
336
337
        if tperr != nil {
338
                return tperr
339
        }
340
341
        if !sameLobby {
342
                hooks.AfterLobbyJoin(server, so, lob, player)
343
        }
344
345
        //check if lobby isn't already in progress (which happens when the player is subbing)
346
        if lob.IsFull() && lob.State != models.LobbyStateInProgress {
347
                lob.State = models.LobbyStateReadyingUp
348
                lob.ReadyUpTimestamp = time.Now().Unix() + 30
349
                lob.Save()
350
351
                time.AfterFunc(time.Second*30, func() {
352
                        lobby := &models.Lobby{}
353
                        db.DB.First(lobby, lob.ID)
354
355
                        //if all player's haven't readied up,
356
                        //remove unreadied players and unready the
357
                        //rest.
358
                        if lobby.State != models.LobbyStateInProgress {
359
                                removeUnreadyPlayers(server, lobby)
360
361
                                lobby.State = models.LobbyStateWaiting
362
                                lobby.Save()
363
                        }
364
                })
365
366
                room := fmt.Sprintf("%s_private",
367
                        hooks.GetLobbyRoom(lob.ID))
368
                broadcaster.SendMessageToRoom(room, "lobbyReadyUp",
369
                        struct {
370
                                Timeout int `json:"timeout"`
371
                        }{30})
372
                models.BroadcastLobbyList()
373
        }
374
375
        if lob.State == models.LobbyStateInProgress {
376
                db.DB.Preload("ServerInfo").First(lob, lob.ID)
377
                so.EmitJSON(helpers.NewRequest("lobbyStart", models.DecorateLobbyConnect(lob, player.Name, *args.Class)))
378
        }
379
380
        return chelpers.EmptySuccessJS
381
}
func removePlayerFromLobby
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go:

451
func removePlayerFromLobby(lobbyId uint, steamId string) (*models.Lobby, *models.Player, *helpers.TPError) {
452
        player, tperr := models.GetPlayerBySteamID(steamId)
453
        if tperr != nil {
454
                return nil, nil, tperr
455
        }
456
457
        lob, tperr := models.GetLobbyByID(lobbyId)
458
        if tperr != nil {
459
                return nil, nil, tperr
460
        }
461
462
        switch lob.State {
463
        case models.LobbyStateInProgress:
464
                return lob, player, helpers.NewTPError("Lobby is in progress.", 1)
465
        case models.LobbyStateEnded:
466
                return lob, player, helpers.NewTPError("Lobby has closed.", 1)
467
        }
468
469
        _, err := lob.GetPlayerSlot(player)
470
        if err != nil {
471
                return lob, player, helpers.NewTPError("Player not playing", 2)
472
        }
473
474
        if err := lob.RemovePlayer(player); err != nil {
475
                return lob, player, err
476
        }
477
478
        return lob, player, lob.AddSpectator(player)
479
}
func Admin.AdminChangeRole
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/admin.go:

33
func (Admin) AdminChangeRole(server *wsevent.Server, so *wsevent.Client, data []byte) interface{} {
34
        reqerr := chelpers.CheckPrivilege(so, helpers.ActionChangeRole)
35
36
        if reqerr != nil {
37
                return reqerr
38
        }
39
        var args struct {
40
                Steamid *string `json:"steamid"`
41
                Role    *string `json:"role"`
42
        }
43
44
        err := chelpers.GetParams(data, &args)
45
        if err != nil {
46
                return helpers.NewTPErrorFromError(err)
47
        }
48
49
        role, ok := helpers.RoleMap[*args.Role]
50
        if !ok || role == helpers.RoleAdmin {
51
                return helpers.NewTPError("Invalid role parameter", 0)
52
        }
53
54
        otherPlayer, tperr := models.GetPlayerBySteamID(*args.Steamid)
55
        if err != nil {
56
                return tperr
57
        }
58
59
        currPlayer, _ := chelpers.GetPlayerSocket(so.Id())
60
61
        models.LogAdminAction(currPlayer.ID, helpers.ActionChangeRole, otherPlayer.ID)
62
63
        // actual change happens
64
        otherPlayer.Role = role
65
        db.DB.Save(&otherPlayer)
66
67
        // rewrite session data. THIS WON'T WRITE A COOKIE SO IT ONLY WORKS WITH
68
        // STORES THAT STORE DATA IN COOKIES (AND NOT ONLY SESSION ID).
69
        session, sesserr := chelpers.GetSessionHTTP(so.Request())
70
        if sesserr == nil {
71
                session.Values["role"] = role
72
                session.Save(so.Request(), FakeResponseWriter{})
73
        }
74
75
        return chelpers.EmptySuccessJS
76
}
func Player.PlayerReady
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/player.go:

17
func (Player) PlayerReady(_ *wsevent.Server, so *wsevent.Client, data []byte) interface{} {
18
        steamid := chelpers.GetSteamId(so.Id())
19
        player, tperr := models.GetPlayerBySteamID(steamid)
20
        if tperr != nil {
21
                return tperr
22
        }
23
24
        lobbyid, tperr := player.GetLobbyID(false)
25
        if tperr != nil {
26
                return tperr
27
        }
28
29
        lobby, tperr := models.GetLobbyByIDServer(lobbyid)
30
        if tperr != nil {
31
                return tperr
32
        }
33
34
        if lobby.State != models.LobbyStateReadyingUp {
35
                return helpers.NewTPError("Lobby hasn't been filled up yet.", 4)
36
        }
37
38
        tperr = lobby.ReadyPlayer(player)
39
40
        if tperr != nil {
41
                return tperr
42
        }
43
44
        if lobby.IsEveryoneReady() {
45
                lobby.Start()
46
47
                hooks.BroadcastLobbyStart(lobby)
48
                models.BroadcastLobbyList()
49
                models.FumbleLobbyStarted(lobby)
50
        }
51
52
        return chelpers.EmptySuccessJS
53
}
func Player.PlayerNotReady
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/player.go:

55
func (Player) PlayerNotReady(server *wsevent.Server, so *wsevent.Client, data []byte) interface{} {
56
        player, tperr := models.GetPlayerBySteamID(chelpers.GetSteamId(so.Id()))
57
58
        if tperr != nil {
59
                return tperr
60
        }
61
62
        lobbyid, tperr := player.GetLobbyID(false)
63
        if tperr != nil {
64
                return tperr
65
        }
66
67
        lobby, tperr := models.GetLobbyByID(lobbyid)
68
        if tperr != nil {
69
                return tperr
70
        }
71
72
        if lobby.State != models.LobbyStateReadyingUp {
73
                return helpers.NewTPError("Lobby hasn't been filled up yet.", 4)
74
        }
75
76
        tperr = lobby.UnreadyPlayer(player)
77
        lobby.RemovePlayer(player)
78
        hooks.AfterLobbyLeave(server, lobby, player)
79
80
        if tperr != nil {
81
                return tperr
82
        }
83
84
        lobby.UnreadyAllPlayers()
85
        return chelpers.EmptySuccessJS
86
}
func Lobby.LobbyServerReset
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go:

180
func (Lobby) LobbyServerReset(server *wsevent.Server, so *wsevent.Client, data []byte) interface{} {
181
        var args struct {
182
                ID *uint `json:"id"`
183
        }
184
185
        if err := chelpers.GetParams(data, &args); err != nil {
186
                return helpers.NewTPErrorFromError(err)
187
        }
188
189
        player, tperr := models.GetPlayerBySteamID(chelpers.GetSteamId(so.Id()))
190
        if tperr != nil {
191
                return tperr
192
        }
193
194
        lobby, tperr := models.GetLobbyByID(*args.ID)
195
196
        if player.SteamID != lobby.CreatedBySteamID || player.Role != helpers.RoleAdmin {
197
                return helpers.NewTPError("Player not authorized to reset server.", -1)
198
        }
199
200
        if tperr != nil {
201
                return tperr
202
        }
203
204
        if lobby.State == models.LobbyStateEnded {
205
                return helpers.NewTPError("Lobby has ended", 1)
206
        }
207
208
        if err := models.ReExecConfig(lobby.ID); err != nil {
209
                return helpers.NewTPErrorFromError(err)
210
        }
211
212
        return chelpers.EmptySuccessJS
213
214
}
func Lobby.ServerVerify
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go:

218
func (Lobby) ServerVerify(server *wsevent.Server, so *wsevent.Client, data []byte) interface{} {
219
        var args struct {
220
                Server  *string `json:"server"`
221
                Rconpwd *string `json:"rconpwd"`
222
        }
223
224
        if err := chelpers.GetParams(data, &args); err != nil {
225
                return helpers.NewTPErrorFromError(err)
226
        }
227
228
        if !validAddress.MatchString(*args.Server) {
229
                return helpers.NewTPError("Invalid Server Address", -1)
230
        }
231
232
        var count int
233
        db.DB.Table("server_records").Where("host = ?", *args.Server).Count(&count)
234
        if count != 0 {
235
                return helpers.NewTPError("A lobby is already using this server.", -1)
236
        }
237
238
        info := &models.ServerRecord{
239
                Host:         *args.Server,
240
                RconPassword: *args.Rconpwd,
241
        }
242
        db.DB.Save(info)
243
        defer db.DB.Where("host = ?", info.Host).Delete(models.ServerRecord{})
244
245
        err := models.VerifyInfo(*info)
246
        if err != nil {
247
                return helpers.NewTPErrorFromError(err)
248
        }
249
250
        return chelpers.EmptySuccessJS
251
}
func Player.PlayerSettingsSet
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/player.go:

117
func (Player) PlayerSettingsSet(server *wsevent.Server, so *wsevent.Client, data []byte) interface{} {
118
        var args struct {
119
                Key   *string `json:"key"`
120
                Value *string `json:"value"`
121
        }
122
123
        err := chelpers.GetParams(data, &args)
124
        if err != nil {
125
                return helpers.NewTPErrorFromError(err)
126
        }
127
128
        player, _ := models.GetPlayerBySteamID(chelpers.GetSteamId(so.Id()))
129
130
        err = player.SetSetting(*args.Key, *args.Value)
131
        if err != nil {
132
                return helpers.NewTPErrorFromError(err)
133
        }
134
135
        if *args.Key == "siteAlias" {
136
                profile := models.DecoratePlayerProfileJson(player)
137
                so.EmitJSON(helpers.NewRequest("playerProfile", profile))
138
139
                if lobbyID, _ := player.GetLobbyID(true); lobbyID != 0 {
140
                        lobby, _ := models.GetLobbyByID(lobbyID)
141
                        lobbyData := lobby.LobbyData(true)
142
                        lobbyData.Send()
143
                }
144
        }
145
146
        return chelpers.EmptySuccessJS
147
}
func Player.PlayerSettingsGet
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/player.go:

88
func (Player) PlayerSettingsGet(server *wsevent.Server, so *wsevent.Client, data []byte) interface{} {
89
        var args struct {
90
                Key *string `json:"key"`
91
        }
92
93
        err := chelpers.GetParams(data, &args)
94
        if err != nil {
95
                return helpers.NewTPErrorFromError(err)
96
        }
97
98
        player, _ := models.GetPlayerBySteamID(chelpers.GetSteamId(so.Id()))
99
100
        var settings []models.PlayerSetting
101
        var setting models.PlayerSetting
102
        if *args.Key == "*" {
103
                settings, err = player.GetSettings()
104
        } else {
105
                setting, err = player.GetSetting(*args.Key)
106
                settings = append(settings, setting)
107
        }
108
109
        if err != nil {
110
                return helpers.NewTPErrorFromError(err)
111
        }
112
113
        result := models.DecoratePlayerSettingsJson(settings)
114
        return chelpers.NewResponse(result)
115
}
func Lobby.LobbyBan
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go:

530
func (Lobby) LobbyBan(server *wsevent.Server, so *wsevent.Client, data []byte) interface{} {
531
        var args struct {
532
                Id      *uint   `json:"id"`
533
                Steamid *string `json:"steamid"`
534
        }
535
536
        if err := chelpers.GetParams(data, &args); err != nil {
537
                return helpers.NewTPErrorFromError(err)
538
        }
539
540
        steamId := *args.Steamid
541
        selfSteamId := chelpers.GetSteamId(so.Id())
542
543
        if steamId == selfSteamId {
544
                return helpers.NewTPError("Player can't kick himself.", -1)
545
        }
546
        if ok, tperr := playerCanKick(*args.Id, selfSteamId); !ok {
547
                return tperr
548
        }
549
550
        lob, player, tperr := removePlayerFromLobby(*args.Id, steamId)
551
        if tperr != nil {
552
                return tperr
553
        }
554
555
        lob.BanPlayer(player)
556
557
        hooks.AfterLobbyLeave(server, lob, player)
558
559
        // broadcaster.SendMessage(steamId, "sendNotification",
560
        //         fmt.Sprintf(`{"notification": "You have been removed from Lobby #%d"}`, *args.Id))
561
562
        return chelpers.EmptySuccessJS
563
}
func newBan
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/ban.go:

13
func newBan(player_steamid, admin_steamid string, action authority.AuthAction, bantype models.PlayerBanType, until int64, reason string) *helpers.TPError {
14
        player, tperr := models.GetPlayerBySteamID(player_steamid)
15
        if tperr != nil {
16
                return tperr
17
        }
18
        admin, tperr := models.GetPlayerBySteamID(admin_steamid)
19
        if tperr != nil {
20
                return tperr
21
        }
22
23
        time := time.Unix(time.Now().Unix()+until, 0)
24
        err := player.BanUntil(time, bantype, reason)
25
26
        if err != nil {
27
                tperr = helpers.NewTPErrorFromError(err)
28
        }
29
30
        err = models.LogAdminAction(admin.ID, action, player.ID)
31
        if err != nil {
32
                return helpers.NewTPErrorFromError(err)
33
        }
34
35
        return nil
36
}
func Lobby.LobbyKick
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go:

497
func (Lobby) LobbyKick(server *wsevent.Server, so *wsevent.Client, data []byte) interface{} {
498
        var args struct {
499
                Id      *uint   `json:"id"`
500
                Steamid *string `json:"steamid"`
501
        }
502
503
        if err := chelpers.GetParams(data, &args); err != nil {
504
                return helpers.NewTPErrorFromError(err)
505
        }
506
507
        steamId := *args.Steamid
508
        selfSteamId := chelpers.GetSteamId(so.Id())
509
510
        if steamId == selfSteamId {
511
                return helpers.NewTPError("Player can't kick himself.", -1)
512
        }
513
        if ok, tperr := playerCanKick(*args.Id, selfSteamId); !ok {
514
                return tperr
515
        }
516
517
        lob, player, tperr := removePlayerFromLobby(*args.Id, steamId)
518
        if tperr != nil {
519
                return tperr
520
        }
521
522
        hooks.AfterLobbyLeave(server, lob, player)
523
524
        // broadcaster.SendMessage(steamId, "sendNotification",
525
        //         fmt.Sprintf(`{"notification": "You have been removed from Lobby #%d"}`, *args.Id))
526
527
        return chelpers.EmptySuccessJS
528
}
func Player.PlayerProfile
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/player.go:

149
func (Player) PlayerProfile(server *wsevent.Server, so *wsevent.Client, data []byte) interface{} {
150
        var args struct {
151
                Steamid *string `json:"steamid"`
152
        }
153
154
        err := chelpers.GetParams(data, &args)
155
        if err != nil {
156
                return helpers.NewTPErrorFromError(err)
157
        }
158
159
        steamid := *args.Steamid
160
        if steamid == "" {
161
                steamid = chelpers.GetSteamId(so.Id())
162
        }
163
164
        player, playErr := models.GetPlayerWithStats(steamid)
165
166
        if playErr != nil {
167
                return playErr
168
        }
169
170
        result := models.DecoratePlayerProfileJson(player)
171
        return chelpers.NewResponse(result)
172
}
func @64:28
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/ban.go:

64
func(_ *wsevent.Server, so *wsevent.Client, data []byte) interface{} {
65
                        reqerr := chelpers.CheckPrivilege(so, ban.action)
66
                        if reqerr != nil {
67
                                return reqerr
68
                        }
69
70
                        var args struct {
71
                                SteamID *string `json:"steamid"`
72
                                Until   *int64  `json:"until"`
73
                                Reason  *string `json:"reason"`
74
                        }
75
76
                        if err := chelpers.GetParams(data, &args); err != nil {
77
                                return helpers.NewTPErrorFromError(err)
78
                        }
79
80
                        steamID := chelpers.GetSteamId(so.Id())
81
82
                        tperr := newBan(*args.SteamID, steamID, ban.action, ban.banType, *args.Until, *args.Reason)
83
                        if tperr != nil {
84
                                return tperr
85
                        }
86
87
                        return chelpers.EmptySuccessJS
88
                }
func @90:33
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/ban.go:

90
func(_ *wsevent.Server, so *wsevent.Client, data []byte) interface{} {
91
                        reqerr := chelpers.CheckPrivilege(so, ban.action)
92
                        if reqerr != nil {
93
                                return reqerr
94
                        }
95
96
                        var args struct {
97
                                SteamID *string `json:"steamid"`
98
                        }
99
100
                        if err := chelpers.GetParams(data, &args); err != nil {
101
                                return helpers.NewTPErrorFromError(err)
102
                        }
103
104
                        err := unban(*args.SteamID, ban.banType)
105
                        if err != nil {
106
                                return err
107
                        }
108
                        return chelpers.EmptySuccessJS
109
                }
func playerCanKick
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go:

481
func playerCanKick(lobbyId uint, steamId string) (bool, *helpers.TPError) {
482
        lob, tperr := models.GetLobbyByID(lobbyId)
483
        if tperr != nil {
484
                return false, tperr
485
        }
486
487
        player, tperr2 := models.GetPlayerBySteamID(steamId)
488
        if tperr2 != nil {
489
                return false, tperr2
490
        }
491
        if steamId != lob.CreatedBySteamID && player.Role != helpers.RoleAdmin {
492
                return false, helpers.NewTPError("Not authorized to kick players", 1)
493
        }
494
        return true, nil
495
}
func removeUnreadyPlayers
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go:

383
func removeUnreadyPlayers(server *wsevent.Server, lobby *models.Lobby) {
384
        var steamIDs []string
385
386
        db.DB.Table("players").Select("players.steam_id").Joins("INNER JOIN lobby_slots ON lobby_slots.player_id = players.id").Where("lobby_slots.lobby_id = ? AND lobby_slots.ready = ?", lobby.ID, false).Find(&steamIDs)
387
        lobby.RemoveUnreadyPlayers(true)
388
389
        for _, steamID := range steamIDs {
390
                sockets, ok := broadcaster.GetSockets(steamID)
391
                if ok {
392
                        for _, so := range sockets {
393
                                server.RemoveClient(so.Id(), fmt.Sprintf("%d_private", lobby.ID))
394
                        }
395
                }
396
        }
397
}
func Global.GetConstant
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/global.go:

21
func (Global) GetConstant(_ *wsevent.Server, so *wsevent.Client, data []byte) interface{} {
22
        var args struct {
23
                Constant string `json:"constant"`
24
        }
25
        if err := chelpers.GetParams(data, &args); err != nil {
26
                return helpers.NewTPErrorFromError(err)
27
        }
28
29
        output := simplejson.New()
30
        switch args.Constant {
31
        case "lobbySettingsList":
32
                output = models.LobbySettingsToJSON()
33
        default:
34
                return helpers.NewTPError("Unknown constant.", -1)
35
        }
36
37
        return chelpers.NewResponse(output)
38
}
func unban
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/ban.go:

38
func unban(steamid string, bantype models.PlayerBanType) *helpers.TPError {
39
        player, tperr := models.GetPlayerBySteamID(steamid)
40
        if tperr != nil {
41
                return tperr
42
        }
43
44
        err := player.Unban(bantype)
45
        if err != nil {
46
                return helpers.NewTPErrorFromError(err)
47
        }
48
49
        return nil
50
}
func @351:34
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go:

351
func() {
352
                        lobby := &models.Lobby{}
353
                        db.DB.First(lobby, lob.ID)
354
355
                        //if all player's haven't readied up,
356
                        //remove unreadied players and unready the
357
                        //rest.
358
                        if lobby.State != models.LobbyStateInProgress {
359
                                removeUnreadyPlayers(server, lobby)
360
361
                                lobby.State = models.LobbyStateWaiting
362
                                lobby.Save()
363
                        }
364
                }
func Lobby.RequestLobbyListData
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/lobby.go:

617
func (Lobby) RequestLobbyListData(_ *wsevent.Server, so *wsevent.Client, data []byte) interface{} {
618
        var lobbies []models.Lobby
619
        db.DB.Where("state = ?", models.LobbyStateWaiting).Order("id desc").Find(&lobbies)
620
        so.EmitJSON(helpers.NewRequest("lobbyListData", models.DecorateLobbyListData(lobbies)))
621
622
        return chelpers.EmptySuccessJS
623
}
func FakeResponseWriter.Write
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/admin.go:

22
func (f FakeResponseWriter) Write(b []byte) (int, error) {
23
        return 0, nil
24
}
func FakeResponseWriter.Header
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/admin.go:

19
func (f FakeResponseWriter) Header() http.Header {
20
        return http.Header{}
21
}
func FakeResponseWriter.WriteHeader
Back

In /home/ubuntu/.go_project/src/github.com/TF2Stadium/Helen/controllers/socket/internal/handler/admin.go:

25
func (f FakeResponseWriter) WriteHeader(int) {}
github.com/TF2Stadium/Helen/controllers/socket/internal/handler
31.76%