├── .gitignore
├── LICENSE
├── README.md
├── go.mod
├── go.sum
├── main.go
└── vasar
├── allower.go
├── board
└── board.go
├── broadcast.go
├── command
├── alias.go
├── announce.go
├── ban.go
├── blacklist.go
├── chat.go
├── cmd.go
├── cps.go
├── disguise.go
├── duel.go
├── entity.go
├── ffa.go
├── fly.go
├── freeze.go
├── game_mode.go
├── global_mute.go
├── kick.go
├── kill.go
├── mute.go
├── nick.go
├── online.go
├── pvp.go
├── rekit.go
├── reply.go
├── report.go
├── reset.go
├── restart.go
├── role.go
├── settings.go
├── spawn.go
├── spectate.go
├── stats.go
├── teleport.go
├── vanish.go
├── variant.go
├── whisper.go
├── whitelist.go
└── who.go
├── config.go
├── data
├── data.go
├── offline.go
└── online.go
├── entity
├── fishing_hook.go
├── lightning.go
├── transform.go
├── vasar_pearl.go
└── vasar_potion.go
├── form
├── advanced_settings.go
├── ban.go
├── blacklist.go
├── casual_stats.go
├── competitive_stats.go
├── display_settings.go
├── duel_request.go
├── duels.go
├── ffa.go
├── gameplay_settings.go
├── matchmaking_settings.go
├── mute.go
├── post_match_stats.go
├── privacy_settings.go
├── settings.go
├── spectate.go
├── toggle_dropdown.go
└── visual_settings.go
├── game
├── ffa
│ └── provider.go
├── game.go
├── healing
│ └── source.go
├── kit
│ ├── boxing.go
│ ├── builduhc.go
│ ├── combo.go
│ ├── debuff.go
│ ├── gapple.go
│ ├── items.go
│ ├── lobby.go
│ ├── nodebuff.go
│ ├── queue.go
│ ├── soup.go
│ ├── stick_fight.go
│ └── sumo.go
├── lobby
│ ├── inventory_handler.go
│ └── provider.go
└── match
│ ├── doc.go
│ ├── grid.go
│ ├── handler.go
│ ├── hits_handler.go
│ ├── match.go
│ ├── provider.go
│ ├── ranked_provider.go
│ ├── rounds_handler.go
│ ├── stats.go
│ ├── structure.go
│ └── unranked_provider.go
├── handler.go
├── item
├── fishing_rod.go
├── potion.go
├── stew.go
├── vasar_pearl.go
└── vasar_potion.go
├── leaderboard.go
├── module
├── click.go
├── combat.go
├── game.go
├── inventory.go
├── moderation.go
├── protection.go
├── rods.go
└── settings.go
├── pearl_handler.go
├── potion_handler.go
├── provider.go
├── skins.go
├── user
├── chat_type.go
├── custom.go
├── device_group.go
├── elo_range.go
├── experience.go
├── input_handler.go
├── ping_range.go
├── punishment.go
├── role.go
├── settings.go
├── stats.go
├── tag.go
└── user.go
├── vasar.go
└── world_handler.go
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea/
2 | /assets/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Vasar DF
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # practice
2 |
3 | Vasar's Practice in Dragonfly
4 |
5 | ## A bit of context
6 |
7 | This is Vasar DF, a project with the sole intent of remaking the PocketMine Vasar but, well, in Dragonfly.
8 |
9 | Released early May, it was severely rushed and thus this code is not a great sight. Unfortunately, the project wasn't a
10 | success either (hence why this is now open source), because the PvP community is extremely picky and is used to how
11 | movement (and as a result, PvP style) works on PocketMine.
12 |
13 | As such, we all decided to end the project and make it open source.
14 |
15 | ## Credits
16 | - Warro, for the original Vasar and for great support while developing Vasar DF
17 | - Prim, developer on Vasar PM & DF along with Oomph, our anti-cheat proxy (not included here)
18 | - Restart, developed significant cosmetic functions on Vasar DF
19 | - Raptor, developed significant functions of the match system on Vasar DF
20 | - Myself, for developing most of the base code for Vasar DF
21 | - Cadet, for emotional support
22 | - Sander, for developing Dragonfly and Gophertunnel
23 | - Ethan, for developing key components of Oomph
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/vasar-network/practice
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/RestartFU/gophig v0.0.0-20220311013807-ab02376aef83
7 | github.com/brandenc40/romannumeral v1.1.5
8 | github.com/df-mc/atomic v1.10.0
9 | github.com/df-mc/dragonfly v0.7.2
10 | github.com/getsentry/sentry-go v0.13.0
11 | github.com/go-gl/mathgl v1.0.0
12 | github.com/google/uuid v1.3.0
13 | github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b
14 | github.com/justtaldevelops/webhook v0.0.0-20220316021311-d970392b85eb
15 | github.com/oomph-ac/oomph v0.0.0-20220509213403-52c6773f6c12
16 | github.com/sandertv/gophertunnel v1.20.0
17 | github.com/sirupsen/logrus v1.8.1
18 | github.com/unickorn/strcenter v0.0.0-20220410230650-16396be2dd9e
19 | github.com/upper/db/v4 v4.5.2
20 | github.com/vasar-network/vails v0.0.0-20220605171309-629f49a58da7
21 | github.com/vasar-network/vap v0.0.0-20220428050151-e146ee9466a7
22 | golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4
23 | golang.org/x/exp v0.0.0-20220328175248-053ad81199eb
24 | golang.org/x/text v0.3.7
25 | )
26 |
27 | require (
28 | github.com/brentp/intintmap v0.0.0-20190211203843-30dc0ade9af9 // indirect
29 | github.com/cespare/xxhash v1.1.0 // indirect
30 | github.com/df-mc/goleveldb v1.1.9 // indirect
31 | github.com/go-yaml/yaml v2.1.0+incompatible // indirect
32 | github.com/golang/protobuf v1.5.2 // indirect
33 | github.com/golang/snappy v0.0.4 // indirect
34 | github.com/klauspost/compress v1.15.1 // indirect
35 | github.com/kr/text v0.2.0 // indirect
36 | github.com/muhammadmuzzammil1998/jsonc v1.0.0 // indirect
37 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
38 | github.com/pelletier/go-toml v1.9.4 // indirect
39 | github.com/rogpeppe/go-internal v1.3.0 // indirect
40 | github.com/sandertv/go-raknet v1.10.9 // indirect
41 | go.uber.org/atomic v1.9.0 // indirect
42 | golang.org/x/image v0.0.0-20220321031419-a8550c1d254a // indirect
43 | golang.org/x/net v0.0.0-20220418201149-a630d4f3e7a2 // indirect
44 | golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
45 | golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f // indirect
46 | google.golang.org/appengine v1.6.7 // indirect
47 | google.golang.org/protobuf v1.28.0 // indirect
48 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
49 | gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 // indirect
50 | gopkg.in/square/go-jose.v2 v2.6.0 // indirect
51 | )
52 |
--------------------------------------------------------------------------------
/vasar/allower.go:
--------------------------------------------------------------------------------
1 | package vasar
2 |
3 | import (
4 | "github.com/hako/durafmt"
5 | "github.com/sandertv/gophertunnel/minecraft/protocol/login"
6 | "github.com/unickorn/strcenter"
7 | "github.com/upper/db/v4"
8 | "github.com/vasar-network/practice/vasar/data"
9 | "github.com/vasar-network/vails/lang"
10 | "github.com/vasar-network/vails/role"
11 | "golang.org/x/text/language"
12 | "net"
13 | "strings"
14 | )
15 |
16 | // allower ensures that all players who join are whitelisted if whitelisting is enabled.
17 | type allower struct {
18 | v *Vasar
19 | }
20 |
21 | // Allow ...
22 | func (a *allower) Allow(_ net.Addr, identity login.IdentityData, client login.ClientData) (string, bool) {
23 | l, _ := language.Parse(strings.Replace(client.LanguageCode, "_", "-", 1))
24 | users, err := data.SearchOfflineUsers(db.Or(db.Cond{"did": client.DeviceID}, db.Cond{"ssid": client.SelfSignedID}, db.Cond{"xuid": identity.XUID}))
25 | if err != nil {
26 | panic(err)
27 | }
28 | for _, u := range users {
29 | if !u.Ban.Expired() {
30 | reason := strings.TrimSpace(u.Ban.Reason)
31 | if u.Ban.Permanent {
32 | description := lang.Translatef(l, "user.blacklist.description", reason)
33 | if u.XUID() == identity.XUID {
34 | return strcenter.CenterLine(lang.Translatef(l, "user.blacklist.header") + "\n" + description), false
35 | }
36 | return strcenter.CenterLine(lang.Translatef(l, "user.blacklist.header.alt") + "\n" + description), false
37 | }
38 | description := lang.Translatef(l, "user.ban.description", reason, durafmt.ParseShort(u.Ban.Remaining()))
39 | if u.XUID() == identity.XUID {
40 | return strcenter.CenterLine(lang.Translatef(l, "user.ban.header") + "\n" + description), false
41 | }
42 | return strcenter.CenterLine(lang.Translatef(l, "user.ban.header.alt") + "\n" + description), false
43 | }
44 | }
45 |
46 | if a.v.config.Vasar.Whitelisted {
47 | u, err := data.LoadOfflineUser(identity.DisplayName)
48 | if err != nil {
49 | return strcenter.CenterLine(lang.Translatef(l, "user.server.whitelist")), false
50 | }
51 | return strcenter.CenterLine(lang.Translatef(l, "user.server.whitelist")), u.Whitelisted || u.Roles.Contains(role.Trial{}, role.Operator{})
52 | }
53 | return "", true
54 | }
55 |
--------------------------------------------------------------------------------
/vasar/board/board.go:
--------------------------------------------------------------------------------
1 | package board
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/player"
5 | "github.com/df-mc/dragonfly/server/player/scoreboard"
6 | "github.com/vasar-network/vails/lang"
7 | )
8 |
9 | // Provider is an interface for providers that provide scoreboards.
10 | type Provider interface {
11 | // SendScoreboard will send a scoreboard to the player.
12 | SendScoreboard(p *player.Player)
13 | }
14 |
15 | // NopProvider is a provider that does not provide a scoreboard.
16 | type NopProvider struct{}
17 |
18 | // SendScoreboard ...
19 | func (NopProvider) SendScoreboard(*player.Player) {}
20 |
21 | // Send sends the specified board type and arguments to the player.
22 | func Send(p *player.Player, board string, args ...any) {
23 | b := scoreboard.New(lang.Translatef(p.Locale(), "scoreboard.title"))
24 | _, _ = b.WriteString(lang.Translatef(p.Locale(), board, args...))
25 | b.RemovePadding()
26 | p.SendScoreboard(b)
27 | }
28 |
--------------------------------------------------------------------------------
/vasar/broadcast.go:
--------------------------------------------------------------------------------
1 | package vasar
2 |
3 | import (
4 | "github.com/vasar-network/practice/vasar/user"
5 | "github.com/vasar-network/vails/lang"
6 | "github.com/vasar-network/vails/role"
7 | "strings"
8 | "time"
9 | )
10 |
11 | // startBroadcasts starts sending a new broadcast every five minutes.
12 | func (v *Vasar) startBroadcasts() {
13 | broadcasts := []string{
14 | "vasar.broadcast.discord",
15 | "vasar.broadcast.store",
16 | "vasar.broadcast.emojis",
17 | "vasar.broadcast.settings",
18 | "vasar.broadcast.duels",
19 | "vasar.broadcast.feedback",
20 | "vasar.broadcast.report",
21 | }
22 |
23 | var cursor int
24 | t := time.NewTicker(time.Minute * 5)
25 | defer t.Stop()
26 | for {
27 | select {
28 | case <-v.c:
29 | return
30 | case <-t.C:
31 | message := broadcasts[cursor]
32 | for _, u := range user.All() {
33 | u.Message("vasar.broadcast.notice", lang.Translate(u.Player().Locale(), message))
34 | }
35 |
36 | if cursor++; cursor == len(broadcasts) {
37 | cursor = 0
38 | }
39 | }
40 | }
41 | }
42 |
43 | // startPlayerBroadcasts starts sending a new player broadcast every five minutes.
44 | func (v *Vasar) startPlayerBroadcasts() {
45 | t := time.NewTicker(time.Minute * 10)
46 | defer t.Stop()
47 | for {
48 | select {
49 | case <-v.c:
50 | return
51 | case <-t.C:
52 | users := user.All()
53 | var plus []string
54 | for _, u := range users {
55 | if u.Roles().Contains(role.Plus{}) {
56 | plus = append(plus, u.DisplayName())
57 | }
58 | }
59 |
60 | for _, u := range users {
61 | u.Message("vasar.broadcast.plus", len(plus), strings.Join(plus, ", "))
62 | }
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/vasar/command/alias.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/cmd"
5 | "github.com/df-mc/dragonfly/server/player"
6 | "github.com/sandertv/gophertunnel/minecraft/text"
7 | "github.com/upper/db/v4"
8 | "github.com/vasar-network/practice/vasar/data"
9 | "github.com/vasar-network/practice/vasar/user"
10 | "github.com/vasar-network/vails/lang"
11 | "github.com/vasar-network/vails/role"
12 | "strings"
13 | )
14 |
15 | // AliasOnline is a command used to check the alt accounts of an online player.
16 | type AliasOnline struct {
17 | Targets []cmd.Target `cmd:"target"`
18 | }
19 |
20 | // AliasOffline is a command used to check the alt accounts of an offline player.
21 | type AliasOffline struct {
22 | Target string `cmd:"target"`
23 | }
24 |
25 | // Run ...
26 | func (a AliasOnline) Run(s cmd.Source, o *cmd.Output) {
27 | l := locale(s)
28 | if len(a.Targets) > 1 {
29 | o.Error(lang.Translatef(l, "command.targets.exceed"))
30 | return
31 | }
32 | target, ok := a.Targets[0].(*player.Player)
33 | if !ok {
34 | o.Error(lang.Translatef(l, "command.target.unknown"))
35 | return
36 | }
37 | u, ok := user.Lookup(target)
38 | if !ok {
39 | o.Error(lang.Translatef(l, "command.target.unknown"))
40 | return
41 | }
42 |
43 | usersIPs, _ := data.SearchOfflineUsers(db.Cond{"address": u.HashedAddress()})
44 | ipNames := names(usersIPs, true)
45 |
46 | usersDID, _ := data.SearchOfflineUsers(db.Cond{"did": u.DeviceID()})
47 | deviceNames := names(usersDID, true)
48 |
49 | usersSSID, _ := data.SearchOfflineUsers(db.Cond{"ssid": u.SelfSignedID()})
50 | ssidNames := names(usersSSID, true)
51 |
52 | g := text.Colourf(" - ")
53 | o.Print(lang.Translatef(l, "command.alias.accounts",
54 | target.Name(), strings.Join(ipNames, g),
55 | target.Name(), strings.Join(deviceNames, g),
56 | target.Name(), strings.Join(ssidNames, g)),
57 | )
58 | }
59 |
60 | // Run ...
61 | func (a AliasOffline) Run(s cmd.Source, o *cmd.Output) {
62 | l := locale(s)
63 | users, _ := data.SearchOfflineUsers(db.Cond{"name": strings.ToLower(a.Target)})
64 | if len(users) == 0 {
65 | o.Error(lang.Translatef(l, "command.target.unknown"))
66 | return
67 | }
68 | u := users[0]
69 |
70 | usersIPs, _ := data.SearchOfflineUsers(db.Cond{"address": u.Address()})
71 | ipNames := names(usersIPs, true)
72 |
73 | usersDID, _ := data.SearchOfflineUsers(db.Cond{"did": u.DeviceID()})
74 | deviceNames := names(usersDID, true)
75 |
76 | usersSSID, _ := data.SearchOfflineUsers(db.Cond{"ssid": u.SelfSignedID()})
77 | ssidNames := names(usersSSID, true)
78 |
79 | g := text.Colourf(" - ")
80 | o.Print(lang.Translatef(l, "command.alias.accounts",
81 | u.DisplayName(), strings.Join(ipNames, g),
82 | u.DisplayName(), strings.Join(deviceNames, g),
83 | u.DisplayName(), strings.Join(ssidNames, g)),
84 | )
85 | }
86 |
87 | // Allow ...
88 | func (AliasOnline) Allow(s cmd.Source) bool {
89 | return allow(s, true, role.Mod{})
90 | }
91 |
92 | // Allow ...
93 | func (AliasOffline) Allow(s cmd.Source) bool {
94 | return allow(s, true, role.Mod{})
95 | }
96 |
--------------------------------------------------------------------------------
/vasar/command/announce.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/cmd"
5 | "github.com/df-mc/dragonfly/server/player/chat"
6 | "github.com/sandertv/gophertunnel/minecraft/text"
7 | "github.com/vasar-network/vails/lang"
8 | "strings"
9 | )
10 |
11 | // Announce is a command that announces a message to the entire server.
12 | type Announce struct {
13 | Message cmd.Varargs `cmd:"message"`
14 | }
15 |
16 | // Run ...
17 | func (a Announce) Run(s cmd.Source, o *cmd.Output) {
18 | l := locale(s)
19 | msg := strings.TrimSpace(string(a.Message))
20 | if len(msg) == 0 {
21 | o.Error(lang.Translatef(l, "message.empty"))
22 | return
23 | }
24 | _, _ = chat.Global.WriteString(text.Colourf(strings.ReplaceAll(msg, "\\n", "\n")))
25 | }
26 |
27 | // Allow ...
28 | func (Announce) Allow(s cmd.Source) bool {
29 | return allow(s, true)
30 | }
31 |
--------------------------------------------------------------------------------
/vasar/command/chat.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/cmd"
5 | "github.com/df-mc/dragonfly/server/player"
6 | "github.com/vasar-network/practice/vasar/user"
7 | "github.com/vasar-network/vails/lang"
8 | "strings"
9 | )
10 |
11 | // Chat is a command used to check the available chats a player can switch to.
12 | type Chat struct{}
13 |
14 | // ChatSwitch is a command used to switch to available chats.
15 | type ChatSwitch struct {
16 | ChatType chatType `cmd:"chat"`
17 | }
18 |
19 | // Run ...
20 | func (c Chat) Run(s cmd.Source, o *cmd.Output) {
21 | p := s.(*player.Player)
22 | t := []string{"Global"}
23 | if u, ok := user.Lookup(p); ok && u.Roles().Staff() {
24 | t = append(t, "Staff")
25 | }
26 | // TODO: Append a party option if the user is in a party, when parties are implemented.
27 | o.Print(lang.Translatef(p.Locale(), "command.chat.available", strings.Join(t, ", ")))
28 | }
29 |
30 | // Run ...
31 | func (c ChatSwitch) Run(s cmd.Source, o *cmd.Output) {
32 | p := s.(*player.Player)
33 | u, ok := user.Lookup(p)
34 | if !ok {
35 | return
36 | }
37 |
38 | var t user.ChatType
39 | switch c.ChatType {
40 | case "global":
41 | t = user.ChatTypeGlobal()
42 | case "staff":
43 | t = user.ChatTypeStaff()
44 | case "party":
45 | t = user.ChatTypeParty()
46 | }
47 | if u.ChatType() == t {
48 | o.Errorf(lang.Translatef(p.Locale(), "command.chat.already", t.String()))
49 | return
50 | }
51 | o.Print(lang.Translatef(p.Locale(), "command.chat.switch", t.String()))
52 | u.UpdateChatType(t)
53 | }
54 |
55 | // Allow ...
56 | func (c Chat) Allow(s cmd.Source) bool {
57 | _, ok := s.(*player.Player)
58 | return ok
59 | }
60 |
61 | // Allow ...
62 | func (c ChatSwitch) Allow(s cmd.Source) bool {
63 | _, ok := s.(*player.Player)
64 | return ok
65 | }
66 |
67 | type chatType string
68 |
69 | // Type ...
70 | func (c chatType) Type() string {
71 | return "chatType"
72 | }
73 |
74 | // Options ...
75 | func (c chatType) Options(s cmd.Source) []string {
76 | o := []string{"global"}
77 | if u, ok := user.Lookup(s.(*player.Player)); ok && u.Roles().Staff() {
78 | o = append(o, "staff")
79 | }
80 | // TODO: Append a party option if the user is in a party, when parties are implemented.
81 | return o
82 | }
83 |
--------------------------------------------------------------------------------
/vasar/command/cmd.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/cmd"
5 | "github.com/df-mc/dragonfly/server/player"
6 | "github.com/sandertv/gophertunnel/minecraft/text"
7 | "github.com/vasar-network/practice/vasar/data"
8 | "github.com/vasar-network/practice/vasar/user"
9 | "github.com/vasar-network/vails"
10 | "github.com/vasar-network/vails/role"
11 | "golang.org/x/text/language"
12 | )
13 |
14 | // locale returns the locale of a cmd.Source.
15 | func locale(s cmd.Source) language.Tag {
16 | if p, ok := s.(*player.Player); ok {
17 | return p.Locale()
18 | }
19 | return language.English
20 | }
21 |
22 | // allow is a helper function for command allowers. It allows users to easily check for the specified roles.
23 | func allow(src cmd.Source, console bool, roles ...vails.Role) bool {
24 | p, ok := src.(*player.Player)
25 | if !ok {
26 | return console
27 | }
28 | u, ok := user.Lookup(p)
29 | return ok && u.Roles().Contains(append(roles, role.Operator{})...)
30 | }
31 |
32 | // names returns a list of formatted names from a list of users. If tag is true a tag will be shown next to the name
33 | // indicating if the user is banned or blacklisted.
34 | func names(users []data.User, tag bool) (names []string) {
35 | for _, u := range users {
36 | if u.Ban.Permanent {
37 | if tag {
38 | names = append(names, text.Colourf("%s [BLACKLISTED]", u.DisplayName()))
39 | } else {
40 | names = append(names, text.Colourf("%s", u.DisplayName()))
41 | }
42 | } else if !u.Ban.Expired() {
43 | if tag {
44 | names = append(names, text.Colourf("%s [BANNED]", u.DisplayName()))
45 | } else {
46 | names = append(names, text.Colourf("%s", u.DisplayName()))
47 | }
48 | } else {
49 | names = append(names, text.Colourf("%s", u.DisplayName()))
50 | }
51 | }
52 | return
53 | }
54 |
--------------------------------------------------------------------------------
/vasar/command/cps.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/cmd"
5 | "github.com/df-mc/dragonfly/server/player"
6 | "github.com/vasar-network/practice/vasar/user"
7 | "github.com/vasar-network/vails/lang"
8 | "github.com/vasar-network/vails/role"
9 | )
10 |
11 | // CPS is a command to start watching the CPS of a user.
12 | type CPS struct {
13 | Targets cmd.Optional[[]cmd.Target] `cmd:"target"`
14 | }
15 |
16 | // Run ...
17 | func (c CPS) Run(s cmd.Source, o *cmd.Output) {
18 | l := locale(s)
19 | u, ok := user.Lookup(s.(*player.Player))
20 | if !ok {
21 | // The user somehow left in the middle of this, so just stop in our tracks.
22 | return
23 | }
24 | targets := c.Targets.LoadOr(nil)
25 | if len(targets) == 0 {
26 | user.Alert(s, "staff.alert.cps.off")
27 | o.Print(lang.Translatef(l, "command.cps.stop"))
28 | u.StopWatchingClicks()
29 | return
30 | }
31 | if len(targets) > 1 {
32 | o.Error(lang.Translatef(l, "command.targets.exceed"))
33 | return
34 | }
35 | target, ok := targets[0].(*player.Player)
36 | if !ok {
37 | o.Error(lang.Translatef(l, "command.target.unknown"))
38 | return
39 | }
40 | t, ok := user.Lookup(target)
41 | if !ok {
42 | o.Error(lang.Translatef(l, "command.target.unknown"))
43 | return
44 | }
45 | if role.Staff(t.Roles().Highest()) {
46 | o.Error(lang.Translatef(l, "command.cps.staff"))
47 | return
48 | }
49 | if watcher, ok := u.WatchingClicks(); ok && watcher == t {
50 | o.Error(lang.Translatef(l, "command.cps.already"))
51 | return
52 | }
53 | user.Alert(s, "staff.alert.cps.on", t.Player().Name())
54 | o.Print(lang.Translatef(l, "command.cps.start", target.Name()))
55 | u.StartWatchingClicks(t)
56 | }
57 |
58 | // Allow ...
59 | func (c CPS) Allow(s cmd.Source) bool {
60 | return allow(s, false, role.Trial{})
61 | }
62 |
--------------------------------------------------------------------------------
/vasar/command/disguise.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/cmd"
5 | "github.com/vasar-network/vails/lang"
6 | "github.com/vasar-network/vails/role"
7 | )
8 |
9 | // Disguise is a command that allows a player to disguise themselves as another player.
10 | type Disguise struct{}
11 |
12 | // Run ...
13 | func (Disguise) Run(s cmd.Source, o *cmd.Output) {
14 | l := locale(s)
15 | o.Error(lang.Translatef(l, "action.unimplemented"))
16 | }
17 |
18 | // Allow ...
19 | func (Disguise) Allow(s cmd.Source) bool {
20 | return allow(s, false, role.Plus{}, role.Trial{}, role.Famous{}, role.Nitro{})
21 | }
22 |
--------------------------------------------------------------------------------
/vasar/command/duel.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/cmd"
5 | "github.com/df-mc/dragonfly/server/player"
6 | "github.com/vasar-network/practice/vasar/form"
7 | "github.com/vasar-network/practice/vasar/game/lobby"
8 | "github.com/vasar-network/practice/vasar/game/match"
9 | "github.com/vasar-network/practice/vasar/user"
10 | "github.com/vasar-network/vails/lang"
11 | )
12 |
13 | // DuelRequest is a command used to request a duel to a specific player.
14 | type DuelRequest struct {
15 | Targets []cmd.Target `cmd:"target"`
16 | }
17 |
18 | // Run ...
19 | func (d DuelRequest) Run(s cmd.Source, o *cmd.Output) {
20 | p := s.(*player.Player)
21 | l := p.Locale()
22 | if _, ok := lobby.LookupProvider(p); !ok {
23 | o.Error(lang.Translatef(l, "user.feature.disabled"))
24 | return
25 | }
26 | if len(d.Targets) > 1 {
27 | o.Error(lang.Translatef(l, "command.targets.exceed"))
28 | return
29 | }
30 | t, ok := d.Targets[0].(*player.Player)
31 | if !ok {
32 | o.Error(lang.Translatef(l, "command.target.unknown"))
33 | return
34 | }
35 | if p == t {
36 | o.Error(lang.Translatef(l, "duel.self"))
37 | return
38 | }
39 | if _, ok := lobby.LookupProvider(t); !ok {
40 | o.Error(lang.Translatef(l, "duel.unavailable"))
41 | return
42 | }
43 | u, ok := user.Lookup(t)
44 | if !ok {
45 | o.Error(lang.Translatef(l, "command.target.unknown"))
46 | return
47 | }
48 | if u.Settings().Privacy.DuelRequests {
49 | p.SendForm(form.NewDuelRequest(u))
50 | return
51 | }
52 | o.Error(lang.Translatef(l, "duel.requests.disabled"))
53 | }
54 |
55 | // Allow ...
56 | func (DuelRequest) Allow(s cmd.Source) bool {
57 | _, ok := s.(*player.Player)
58 | return ok
59 | }
60 |
61 | // DuelRespond is a command used to accept or deny a duel request last sent to the player.
62 | type DuelRespond struct {
63 | Response response `cmd:"response"`
64 | }
65 |
66 | // Run ...
67 | func (d DuelRespond) Run(s cmd.Source, o *cmd.Output) {
68 | if p := s.(*player.Player); d.Response == "accept" {
69 | if _, ok := lobby.LookupProvider(p); !ok {
70 | o.Error(lang.Translatef(p.Locale(), "duel.match"))
71 | return
72 | }
73 | if !match.Unranked().AcceptDuel(p) {
74 | o.Print(lang.Translatef(p.Locale(), "duel.none"))
75 | }
76 | } else if !match.Unranked().DeclineDuel(p) {
77 | o.Print(lang.Translatef(p.Locale(), "duel.none"))
78 | }
79 | }
80 |
81 | // Allow ...
82 | func (DuelRespond) Allow(s cmd.Source) bool {
83 | _, ok := s.(*player.Player)
84 | return ok
85 | }
86 |
87 | // response ...
88 | type response string
89 |
90 | // Type ...
91 | func (r response) Type() string {
92 | return "duelResponse"
93 | }
94 |
95 | // Options ...
96 | func (r response) Options(cmd.Source) []string {
97 | return []string{"accept", "decline"}
98 | }
99 |
--------------------------------------------------------------------------------
/vasar/command/entity.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/cmd"
5 | "github.com/df-mc/dragonfly/server/player"
6 | )
7 |
8 | // Entity is a command that spawns in a fake player entity.
9 | type Entity struct {
10 | Name string `cmd:"name"`
11 | }
12 |
13 | // Run ...
14 | func (e Entity) Run(s cmd.Source, _ *cmd.Output) {
15 | p := s.(*player.Player)
16 | p.World().AddEntity(player.New(e.Name, p.Skin(), p.Position()))
17 | }
18 |
19 | // Allow ...
20 | func (Entity) Allow(s cmd.Source) bool {
21 | return allow(s, false)
22 | }
23 |
--------------------------------------------------------------------------------
/vasar/command/ffa.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/cmd"
5 | "github.com/vasar-network/practice/vasar/game"
6 | "github.com/vasar-network/practice/vasar/game/ffa"
7 | "github.com/vasar-network/vails/lang"
8 | )
9 |
10 | // FFA is a command that allows an operator to enable or disable an FFA arena.
11 | type FFA struct {
12 | Arena arena `cmd:"arena"`
13 | }
14 |
15 | // Run ...
16 | func (f FFA) Run(s cmd.Source, o *cmd.Output) {
17 | l := locale(s)
18 | for _, prov := range ffa.Providers() {
19 | if prov.Game().String() == string(f.Arena) {
20 | if !prov.ToggleStatus() {
21 | o.Print(lang.Translatef(l, "command.arena.opened", prov.Game().Name()))
22 | return
23 | }
24 | for _, p := range prov.Players() {
25 | prov.RemovePlayer(p, false)
26 | }
27 | o.Print(lang.Translatef(l, "command.arena.closed", prov.Game().Name()))
28 | break
29 | }
30 | }
31 | }
32 |
33 | // Allow ...
34 | func (FFA) Allow(s cmd.Source) bool {
35 | return allow(s, true)
36 | }
37 |
38 | type (
39 | arena string
40 | )
41 |
42 | // Type ...
43 | func (arena) Type() string {
44 | return "arena"
45 | }
46 |
47 | // Options ...
48 | func (arena) Options(cmd.Source) (arenas []string) {
49 | for _, g := range game.FFA() {
50 | arenas = append(arenas, g.String())
51 | }
52 | return
53 | }
54 |
--------------------------------------------------------------------------------
/vasar/command/fly.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/cmd"
5 | "github.com/df-mc/dragonfly/server/player"
6 | "github.com/df-mc/dragonfly/server/world"
7 | "github.com/vasar-network/practice/vasar/game/lobby"
8 | "github.com/vasar-network/vails/lang"
9 | "github.com/vasar-network/vails/role"
10 | )
11 |
12 | // Fly is a command that allows the player to fly in spawn.
13 | type Fly struct{}
14 |
15 | // Run ...
16 | func (Fly) Run(s cmd.Source, o *cmd.Output) {
17 | p := s.(*player.Player)
18 | if _, ok := lobby.LookupProvider(p); !ok {
19 | o.Print(lang.Translatef(p.Locale(), "user.feature.disabled"))
20 | return
21 | }
22 | if f, ok := p.GameMode().(flyGameMode); ok {
23 | o.Print(lang.Translatef(p.Locale(), "command.fly.disabled"))
24 | p.SetGameMode(f.GameMode)
25 | return
26 | }
27 | o.Print(lang.Translatef(p.Locale(), "command.fly.enabled"))
28 | p.SetGameMode(flyGameMode{GameMode: p.GameMode()})
29 | }
30 |
31 | // Allow ...
32 | func (Fly) Allow(s cmd.Source) bool {
33 | return allow(s, false, role.Plus{})
34 | }
35 |
36 | // flyGameMode is a game mode that allows the player to fly.
37 | type flyGameMode struct {
38 | world.GameMode
39 | }
40 |
41 | func (flyGameMode) AllowsFlying() bool { return true }
42 |
--------------------------------------------------------------------------------
/vasar/command/freeze.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/cmd"
5 | "github.com/df-mc/dragonfly/server/player"
6 | "github.com/vasar-network/practice/vasar/user"
7 | "github.com/vasar-network/vails/lang"
8 | "github.com/vasar-network/vails/role"
9 | )
10 |
11 | // Freeze is a command used to freeze a player.
12 | type Freeze struct {
13 | Targets []cmd.Target `cmd:"target"`
14 | }
15 |
16 | // Run ...
17 | func (f Freeze) Run(s cmd.Source, o *cmd.Output) {
18 | l := locale(s)
19 | if len(f.Targets) > 1 {
20 | o.Error(lang.Translatef(l, "command.targets.exceed"))
21 | return
22 | }
23 | target, ok := f.Targets[0].(*player.Player)
24 | if !ok {
25 | o.Error(lang.Translatef(l, "command.target.unknown"))
26 | return
27 | }
28 | if s == target {
29 | o.Error(lang.Translatef(l, "command.usage.self"))
30 | return
31 | }
32 | t, ok := user.Lookup(target)
33 | if !ok {
34 | o.Error(lang.Translatef(l, "command.target.unknown"))
35 | return
36 | }
37 | if t.Frozen() {
38 | user.Alert(s, "staff.alert.unfreeze", target.Name())
39 | o.Print(lang.Translatef(l, "command.freeze.unfreeze", target.Name()))
40 | t.Player().Message(lang.Translatef(t.Player().Locale(), "command.freeze.unfrozen"))
41 | } else {
42 | user.Alert(s, "staff.alert.freeze", target.Name())
43 | o.Print(lang.Translatef(l, "command.freeze.freeze", target.Name()))
44 | t.Player().Message(lang.Translatef(t.Player().Locale(), "command.freeze.frozen"))
45 | }
46 | t.ToggleFreeze()
47 | }
48 |
49 | // Allow ...
50 | func (f Freeze) Allow(s cmd.Source) bool {
51 | return allow(s, true, role.Trial{})
52 | }
53 |
--------------------------------------------------------------------------------
/vasar/command/game_mode.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "github.com/vasar-network/practice/vasar/user"
5 | "github.com/vasar-network/vails/lang"
6 | "github.com/vasar-network/vails/role"
7 | "strings"
8 |
9 | "github.com/df-mc/dragonfly/server/cmd"
10 | "github.com/df-mc/dragonfly/server/player"
11 | "github.com/df-mc/dragonfly/server/world"
12 | )
13 |
14 | // GameMode is a command for a player to change their own game mode or another player's.
15 | type GameMode struct {
16 | GameMode gameMode `cmd:"gamemode"`
17 | Targets cmd.Optional[[]cmd.Target] `cmd:"target"`
18 | }
19 |
20 | // Run ...
21 | func (g GameMode) Run(s cmd.Source, o *cmd.Output) {
22 | var name string
23 | var mode world.GameMode
24 | switch strings.ToLower(string(g.GameMode)) {
25 | case "survival", "0", "s":
26 | name, mode = "survival", world.GameModeSurvival
27 | case "creative", "1", "c":
28 | name, mode = "creative", world.GameModeCreative
29 | case "adventure", "2", "a":
30 | name, mode = "adventure", world.GameModeAdventure
31 | case "spectator", "3", "sp":
32 | name, mode = "spectator", world.GameModeSpectator
33 | }
34 |
35 | l := locale(s)
36 | targets := g.Targets.LoadOr(nil)
37 | if len(targets) > 1 {
38 | o.Error(lang.Translatef(l, "command.targets.exceed"))
39 | return
40 | }
41 | if len(targets) == 1 {
42 | target, ok := targets[0].(*player.Player)
43 | if !ok {
44 | o.Error(lang.Translatef(l, "command.target.unknown"))
45 | return
46 | }
47 |
48 | user.Alert(s, "staff.alert.gamemode.change.other", target.Name(), name)
49 |
50 | target.SetGameMode(mode)
51 | o.Printf(lang.Translatef(l, "command.gamemode.update.other", target.Name(), name))
52 | return
53 | }
54 | if p, ok := s.(*player.Player); ok {
55 | user.Alert(s, "staff.alert.gamemode.change", name)
56 |
57 | p.SetGameMode(mode)
58 | o.Printf(lang.Translatef(l, "command.gamemode.update.self", name))
59 | return
60 | }
61 | o.Error(lang.Translatef(l, "command.gamemode.console"))
62 | }
63 |
64 | // Allow ...
65 | func (GameMode) Allow(s cmd.Source) bool {
66 | return allow(s, true, role.Manager{})
67 | }
68 |
69 | type gameMode string
70 |
71 | // Type ...
72 | func (gameMode) Type() string {
73 | return "GameMode"
74 | }
75 |
76 | // Options ...
77 | func (gameMode) Options(cmd.Source) []string {
78 | return []string{
79 | "survival", "0", "s",
80 | "creative", "1", "c",
81 | "adventure", "2", "a",
82 | "spectator", "3", "sp",
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/vasar/command/global_mute.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/cmd"
5 | "github.com/vasar-network/practice/vasar"
6 | "github.com/vasar-network/vails/lang"
7 | "github.com/vasar-network/vails/role"
8 | )
9 |
10 | // GlobalMute is a command that globally mutes the chat.
11 | type GlobalMute struct {
12 | v *vasar.Vasar
13 | }
14 |
15 | // NewGlobalMute ...
16 | func NewGlobalMute(v *vasar.Vasar) GlobalMute {
17 | return GlobalMute{v: v}
18 | }
19 |
20 | // Run ...
21 | func (g GlobalMute) Run(s cmd.Source, o *cmd.Output) {
22 | l := locale(s)
23 | if g.v.ToggleGlobalMute() {
24 | o.Printf(lang.Translatef(l, "command.globalmute.disabled"))
25 | } else {
26 | o.Printf(lang.Translatef(l, "command.globalmute.enabled"))
27 | }
28 | }
29 |
30 | // Allow ...
31 | func (GlobalMute) Allow(s cmd.Source) bool {
32 | return allow(s, true, role.Manager{})
33 | }
34 |
--------------------------------------------------------------------------------
/vasar/command/kick.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "fmt"
5 | "github.com/df-mc/dragonfly/server/cmd"
6 | "github.com/df-mc/dragonfly/server/player"
7 | hook "github.com/justtaldevelops/webhook"
8 | "github.com/vasar-network/practice/vasar/user"
9 | "github.com/vasar-network/vails/lang"
10 | "github.com/vasar-network/vails/role"
11 | "github.com/vasar-network/vails/webhook"
12 | "strings"
13 | )
14 |
15 | // Kick is a command that disconnects another player from the server.
16 | type Kick struct {
17 | Targets []cmd.Target `cmd:"target"`
18 | }
19 |
20 | // Run ...
21 | func (k Kick) Run(s cmd.Source, o *cmd.Output) {
22 | l, single := locale(s), true
23 | if len(k.Targets) > 1 {
24 | if p, ok := s.(*player.Player); ok {
25 | if u, ok := user.Lookup(p); ok && !u.Roles().Contains(role.Operator{}) {
26 | o.Error(lang.Translatef(l, "command.targets.exceed"))
27 | return
28 | }
29 | }
30 | single = false
31 | }
32 |
33 | var kicked int
34 | for _, p := range k.Targets {
35 | if p, ok := p.(*player.Player); ok {
36 | u, ok := user.Lookup(p)
37 | if !ok || u.Roles().Contains(role.Operator{}) {
38 | o.Print(lang.Translatef(l, "command.kick.fail"))
39 | continue
40 | }
41 | p.Disconnect(lang.Translatef(p.Locale(), "command.kick.reason"))
42 | if single {
43 | webhook.SendPunishment(s.Name(), p.Name(), "", "Kick")
44 | o.Print(lang.Translatef(l, "command.kick.success", p.Name()))
45 | return
46 | }
47 | kicked++
48 | } else if single {
49 | o.Print(lang.Translatef(l, "command.target.unknown"))
50 | return
51 | }
52 | }
53 | if !single {
54 | return
55 | }
56 | webhook.Send(webhook.Punishments, hook.Webhook{
57 | Embeds: []hook.Embed{
58 | {
59 | Title: "Kick (Practice)",
60 | Description: strings.Join([]string{
61 | fmt.Sprintf("Staff: **`%s`**", s.Name()),
62 | fmt.Sprintf("Kicked: **`%v players`**", kicked),
63 | }, "\n"),
64 | Color: 0xFF0000,
65 | },
66 | },
67 | })
68 | o.Print(lang.Translatef(l, "command.kick.multiple", kicked))
69 | }
70 |
71 | // Allow ...
72 | func (Kick) Allow(s cmd.Source) bool {
73 | return allow(s, true, role.Trial{})
74 | }
75 |
--------------------------------------------------------------------------------
/vasar/command/kill.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/cmd"
5 | "github.com/df-mc/dragonfly/server/player"
6 | "github.com/vasar-network/practice/vasar/user"
7 | "github.com/vasar-network/vails/lang"
8 | "github.com/vasar-network/vails/role"
9 | )
10 |
11 | // Kill is a command that allows the player to kill themselves or another player if they have the permission.
12 | type Kill struct {
13 | Targets cmd.Optional[[]cmd.Target] `cmd:"target"`
14 | }
15 |
16 | // suicide is a custom damage source for suicidal people.
17 | type suicide struct{}
18 |
19 | // ReducedByArmour ...
20 | func (s suicide) ReducedByArmour() bool {
21 | return false
22 | }
23 |
24 | // ReducedByResistance ...
25 | func (s suicide) ReducedByResistance() bool {
26 | return false
27 | }
28 |
29 | // Run ...
30 | func (k Kill) Run(s cmd.Source, o *cmd.Output) {
31 | p := s.(*player.Player)
32 | l := locale(p)
33 | if targets := k.Targets.LoadOr(nil); len(targets) > 0 {
34 | u, ok := user.Lookup(p)
35 | if !ok {
36 | o.Error(lang.Translatef(l, "command.target.unknown"))
37 | return
38 | }
39 | if !u.Roles().Contains(role.Operator{}) {
40 | o.Error(lang.Translatef(l, "command.kill.disabled"))
41 | return
42 | }
43 | for _, tI := range targets {
44 | t, ok := tI.(*player.Player)
45 | if !ok {
46 | o.Error(lang.Translatef(l, "command.target.unknown"))
47 | return
48 | }
49 | t.Hurt(t.MaxHealth(), suicide{})
50 | }
51 | } else {
52 | p.Hurt(p.MaxHealth(), suicide{})
53 | }
54 | }
55 |
56 | // Allow ...
57 | func (Kill) Allow(s cmd.Source) bool {
58 | _, ok := s.(*player.Player)
59 | return ok
60 | }
61 |
--------------------------------------------------------------------------------
/vasar/command/online.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "fmt"
5 | "github.com/df-mc/dragonfly/server/cmd"
6 | "github.com/vasar-network/practice/vasar/user"
7 | "github.com/vasar-network/vails/lang"
8 | "github.com/vasar-network/vails/role"
9 | "strings"
10 | )
11 |
12 | // Online is a command that displays the number of players online and their names.
13 | type Online struct{}
14 |
15 | // Run ...
16 | func (Online) Run(s cmd.Source, o *cmd.Output) {
17 | var users []string
18 | for _, u := range user.All() {
19 | name := u.Player().Name()
20 | if name != u.DisplayName() {
21 | name += fmt.Sprintf("(%s)", u.DisplayName())
22 | }
23 | highest := u.Roles().Highest()
24 | tag := highest.Tag(u.DisplayName())
25 | if _, ok := highest.(role.Plus); ok {
26 | tag = strings.ReplaceAll(tag, "§0", u.Settings().Advanced.VasarPlusColour)
27 | }
28 | users = append(users, tag)
29 | }
30 | o.Printf(lang.Translatef(locale(s), "command.online.users", len(users), strings.Join(users, ", ")))
31 | }
32 |
33 | // Allow ...
34 | func (Online) Allow(s cmd.Source) bool {
35 | return allow(s, true)
36 | }
37 |
--------------------------------------------------------------------------------
/vasar/command/pvp.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/cmd"
5 | "github.com/vasar-network/practice/vasar"
6 | "github.com/vasar-network/practice/vasar/game"
7 | "github.com/vasar-network/practice/vasar/game/ffa"
8 | "github.com/vasar-network/practice/vasar/user"
9 | )
10 |
11 | // PvP is a command that lets a user enable or disable pvp in any FFA arena.
12 | type PvP struct {
13 | v *vasar.Vasar
14 | Game gameType `cmd:"game"`
15 | }
16 |
17 | // NewPvP ...
18 | func NewPvP(v *vasar.Vasar) PvP {
19 | return PvP{v: v}
20 | }
21 |
22 | // Run ...
23 | func (p PvP) Run(s cmd.Source, _ *cmd.Output) {
24 | if string(p.Game) == "global" {
25 | if !p.v.TogglePvP() {
26 | user.Broadcast("command.pvp.enable.global", s.Name())
27 | return
28 | }
29 | user.Broadcast("command.pvp.disable.global", s.Name())
30 | return
31 | }
32 | g := game.ByString(string(p.Game))
33 | for _, p := range ffa.Providers() {
34 | if p.Game() == g {
35 | if !p.TogglePvP() {
36 | user.Broadcast("command.pvp.enable.mode", s.Name(), g.Name())
37 | return
38 | }
39 | user.Broadcast("command.pvp.disable.mode", s.Name(), g.Name())
40 | return
41 | }
42 | }
43 | }
44 |
45 | // gameType ...
46 | type gameType string
47 |
48 | // Type ...
49 | func (gameType) Type() string { return "game" }
50 |
51 | // Options ...
52 | func (gameType) Options(cmd.Source) []string {
53 | options := []string{"global"}
54 | for _, g := range game.FFA() {
55 | options = append(options, g.String())
56 | }
57 | return options
58 | }
59 |
60 | // Allow ...
61 | func (PvP) Allow(s cmd.Source) bool {
62 | return allow(s, true)
63 | }
64 |
--------------------------------------------------------------------------------
/vasar/command/rekit.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/cmd"
5 | "github.com/df-mc/dragonfly/server/player"
6 | "github.com/vasar-network/practice/vasar/game/ffa"
7 | "github.com/vasar-network/practice/vasar/game/kit"
8 | "github.com/vasar-network/vails/lang"
9 | )
10 |
11 | // Rekit is a command that gives the user the kit of the FFA game they are currently in.
12 | type Rekit struct{}
13 |
14 | // Run ...
15 | func (Rekit) Run(s cmd.Source, o *cmd.Output) {
16 | p := s.(*player.Player)
17 | if prov, ok := ffa.LookupProvider(p); ok {
18 | kit.Apply(prov.Game().Kit(true), p)
19 | return
20 | }
21 | o.Error(lang.Translatef(p.Locale(), "user.feature.disabled"))
22 | }
23 |
24 | // Allow ...
25 | func (Rekit) Allow(s cmd.Source) bool {
26 | _, ok := s.(*player.Player)
27 | return ok
28 | }
29 |
--------------------------------------------------------------------------------
/vasar/command/reply.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/cmd"
5 | "github.com/df-mc/dragonfly/server/player"
6 | "github.com/sandertv/gophertunnel/minecraft/text"
7 | "github.com/vasar-network/practice/vasar/user"
8 | "github.com/vasar-network/vails/lang"
9 | "github.com/vasar-network/vails/role"
10 | "strings"
11 | )
12 |
13 | // Reply is a command that allows a player to reply to their most recent private message.
14 | type Reply struct {
15 | Message cmd.Varargs `cmd:"message"`
16 | }
17 |
18 | // Run ...
19 | func (r Reply) Run(s cmd.Source, o *cmd.Output) {
20 | u, ok := user.Lookup(s.(*player.Player))
21 | if !ok {
22 | // The user somehow left in the middle of this, so just stop in our tracks.
23 | return
24 | }
25 | l := u.Player().Locale()
26 | if !u.Settings().Privacy.PrivateMessages {
27 | o.Error(lang.Translatef(l, "user.whisper.disabled"))
28 | return
29 | }
30 | msg := strings.TrimSpace(string(r.Message))
31 | if len(msg) <= 0 {
32 | o.Error(lang.Translatef(l, "message.empty"))
33 | return
34 | }
35 |
36 | t, ok := u.LastMessageFrom()
37 | if !ok {
38 | o.Error(lang.Translatef(l, "command.reply.none"))
39 | return
40 | }
41 | if !t.Settings().Privacy.PrivateMessages {
42 | o.Error(lang.Translatef(l, "target.whisper.disabled"))
43 | return
44 | }
45 |
46 | uTag, uMsg := text.Colourf("%s", u.DisplayName()), text.Colourf("%s", msg)
47 | tTag, tMsg := text.Colourf("%s", t.DisplayName()), text.Colourf("%s", msg)
48 | if _, ok := u.Roles().Highest().(role.Default); !ok {
49 | uMsg = t.Roles().Highest().Tag(msg)
50 | uTag = u.Roles().Highest().Tag(u.DisplayName())
51 | }
52 | if _, ok := t.Roles().Highest().(role.Default); !ok {
53 | tMsg = u.Roles().Highest().Tag(msg)
54 | tTag = t.Roles().Highest().Tag(t.DisplayName())
55 | }
56 |
57 | t.SetLastMessageFrom(u.Player())
58 | t.SendCustomSound("random.orb", 1, 1, false)
59 | u.Message("command.whisper.to", tTag, tMsg)
60 | t.Message("command.whisper.from", uTag, uMsg)
61 | }
62 |
63 | // Allow ...
64 | func (Reply) Allow(s cmd.Source) bool {
65 | _, ok := s.(*player.Player)
66 | return ok
67 | }
68 |
--------------------------------------------------------------------------------
/vasar/command/report.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "fmt"
5 | "github.com/df-mc/dragonfly/server/cmd"
6 | "github.com/df-mc/dragonfly/server/player"
7 | hook "github.com/justtaldevelops/webhook"
8 | "github.com/vasar-network/practice/vasar/user"
9 | "github.com/vasar-network/vails/lang"
10 | "github.com/vasar-network/vails/webhook"
11 | "golang.org/x/text/cases"
12 | "golang.org/x/text/language"
13 | "strings"
14 | "time"
15 | )
16 |
17 | // Report is a command used to report other players.
18 | type Report struct {
19 | Targets []cmd.Target `cmd:"target"`
20 | Reason reason `cmd:"reason"`
21 | }
22 |
23 | // Run ...
24 | func (r Report) Run(s cmd.Source, o *cmd.Output) {
25 | l := locale(s)
26 | u, ok := user.Lookup(s.(*player.Player))
27 | if !ok {
28 | // User somehow left midway through, just stop in our tracks.
29 | return
30 | }
31 | if len(r.Targets) < 1 {
32 | o.Error(lang.Translatef(l, "command.targets.exceed"))
33 | return
34 | }
35 | t, ok := r.Targets[0].(*player.Player)
36 | if !ok {
37 | o.Error(lang.Translatef(l, "command.target.unknown"))
38 | return
39 | }
40 | if s == t {
41 | o.Error(lang.Translatef(l, "command.report.self"))
42 | return
43 | }
44 | if exp := u.ReportSince().Add(time.Minute); exp.After(time.Now()) {
45 | o.Error(lang.Translatef(l, "command.report.cooldown", time.Until(exp).Round(time.Millisecond*10)))
46 | return
47 | }
48 | u.RenewReportSince()
49 | o.Print(lang.Translatef(l, "command.report.success"))
50 | user.Alert(s, "staff.alert.report", t.Name(), r.Reason)
51 | webhook.Send(webhook.Report, hook.Webhook{
52 | Embeds: []hook.Embed{{
53 | Title: "Report (Practice)",
54 | Color: 0xFFFFFF,
55 | Description: strings.Join([]string{
56 | fmt.Sprintf("**Player:** %v", t.Name()),
57 | fmt.Sprintf("**Reporter:** %v", s.Name()),
58 | fmt.Sprintf("**Reason:** %v", cases.Title(language.English).String(string(r.Reason))),
59 | }, "\n"),
60 | }},
61 | })
62 | }
63 |
64 | // Allow ...
65 | func (Report) Allow(s cmd.Source) bool {
66 | _, ok := s.(*player.Player)
67 | return ok
68 | }
69 |
70 | type reason string
71 |
72 | // Type ...
73 | func (reason) Type() string {
74 | return "reason"
75 | }
76 |
77 | // Options ...
78 | func (reason) Options(cmd.Source) []string {
79 | return []string{
80 | "cheating",
81 | "teaming",
82 | "interfering",
83 | "spam",
84 | "threats",
85 | "glitching",
86 | "exploiting",
87 | "toxic",
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/vasar/command/restart.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/cmd"
5 | "github.com/vasar-network/practice/vasar/user"
6 | "github.com/vasar-network/vails/lang"
7 | "os"
8 | "syscall"
9 | "time"
10 | )
11 |
12 | // Restart is a command used to restart the server.
13 | type Restart struct{}
14 |
15 | // Run ...
16 | func (Restart) Run(s cmd.Source, _ *cmd.Output) {
17 | l := locale(s)
18 |
19 | o := &cmd.Output{}
20 | o.Print(lang.Translatef(l, "command.restart.begins"))
21 | s.SendCommandOutput(o)
22 |
23 | user.Broadcast("command.restart.countdown", 15)
24 |
25 | expected := time.Now().Add(time.Second * 15)
26 | time.AfterFunc(time.Second*4, func() {
27 | ticker := time.NewTicker(time.Second)
28 | defer ticker.Stop()
29 | for now := range ticker.C {
30 | remaining := expected.Sub(now).Round(time.Second)
31 | user.Broadcast("command.restart.countdown", remaining.Seconds())
32 | if now.After(expected) {
33 | if p, err := os.FindProcess(os.Getpid()); err != nil {
34 | panic(err)
35 | } else if err = p.Signal(syscall.SIGINT); err != nil {
36 | panic(err)
37 | }
38 | return
39 | }
40 | }
41 | })
42 | }
43 |
44 | // Allow ...
45 | func (Restart) Allow(s cmd.Source) bool {
46 | return allow(s, true)
47 | }
48 |
--------------------------------------------------------------------------------
/vasar/command/settings.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/cmd"
5 | "github.com/df-mc/dragonfly/server/player"
6 | "github.com/vasar-network/practice/vasar/form"
7 | "github.com/vasar-network/practice/vasar/user"
8 | )
9 |
10 | // Settings is a command that sends the settings form to the user.
11 | type Settings struct{}
12 |
13 | // Run ...
14 | func (r Settings) Run(s cmd.Source, _ *cmd.Output) {
15 | if u, ok := user.Lookup(s.(*player.Player)); ok {
16 | u.Player().SendForm(form.NewSettings(u))
17 | }
18 | }
19 |
20 | // Allow ...
21 | func (Settings) Allow(s cmd.Source) bool {
22 | _, ok := s.(*player.Player)
23 | return ok
24 | }
25 |
--------------------------------------------------------------------------------
/vasar/command/spawn.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/cmd"
5 | "github.com/df-mc/dragonfly/server/player"
6 | "github.com/vasar-network/practice/vasar/game/ffa"
7 | "github.com/vasar-network/practice/vasar/game/lobby"
8 | "github.com/vasar-network/practice/vasar/game/match"
9 | "github.com/vasar-network/vails/lang"
10 | )
11 |
12 | // Spawn is a command that teleports the player to the spawn.
13 | type Spawn struct{}
14 |
15 | // Run ...
16 | func (Spawn) Run(s cmd.Source, o *cmd.Output) {
17 | l := locale(s)
18 | p := s.(*player.Player)
19 | if prov, ok := ffa.LookupProvider(p); ok {
20 | prov.RemovePlayer(p, false)
21 | return
22 | } else if m, ok := match.Lookup(p); ok {
23 | if !m.RemovePlayer(p, true, true) {
24 | o.Print(lang.Translatef(l, "user.feature.disabled"))
25 | }
26 | return
27 | } else if m, ok := match.Spectating(p); ok {
28 | m.RemoveSpectator(p, false)
29 | return
30 | }
31 | lobby.Lobby().AddPlayer(p)
32 | }
33 |
34 | // Allow ...
35 | func (Spawn) Allow(s cmd.Source) bool {
36 | _, ok := s.(*player.Player)
37 | return ok
38 | }
39 |
--------------------------------------------------------------------------------
/vasar/command/spectate.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/cmd"
5 | "github.com/df-mc/dragonfly/server/player"
6 | "github.com/vasar-network/practice/vasar/form"
7 | "github.com/vasar-network/practice/vasar/game/lobby"
8 | "github.com/vasar-network/vails/lang"
9 | )
10 |
11 | // Spectate ...
12 | type Spectate struct{}
13 |
14 | // Run ...
15 | func (s Spectate) Run(source cmd.Source, output *cmd.Output) {
16 | p := source.(*player.Player)
17 | if _, ok := lobby.LookupProvider(p); !ok {
18 | output.Error(lang.Translatef(p.Locale(), "user.feature.disabled"))
19 | return
20 | }
21 | p.SendForm(form.NewSpectate())
22 | }
23 |
24 | // Allow ...
25 | func (Spectate) Allow(s cmd.Source) bool {
26 | _, ok := s.(*player.Player)
27 | return ok
28 | }
29 |
--------------------------------------------------------------------------------
/vasar/command/stats.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/cmd"
5 | "github.com/df-mc/dragonfly/server/player"
6 | "github.com/vasar-network/practice/vasar/data"
7 | "github.com/vasar-network/practice/vasar/form"
8 | "github.com/vasar-network/practice/vasar/user"
9 | "github.com/vasar-network/vails/lang"
10 | )
11 |
12 | // Stats is a command that displays the stats of a player.
13 | type Stats struct {
14 | Targets cmd.Optional[[]cmd.Target] `cmd:"target"`
15 | }
16 |
17 | // StatsOffline is a command that displays the stats of an offline player.
18 | type StatsOffline struct {
19 | Target cmd.Optional[string] `cmd:"target"`
20 | }
21 |
22 | // Run ...
23 | func (st Stats) Run(s cmd.Source, o *cmd.Output) {
24 | p := s.(*player.Player)
25 | targets := st.Targets.LoadOr(nil)
26 | if len(targets) <= 0 {
27 | f, _ := form.NewCasualStats(p, p.XUID())
28 | p.SendForm(f)
29 | return
30 | }
31 | if len(targets) > 1 {
32 | o.Error(lang.Translatef(p.Locale(), "command.targets.exceed"))
33 | return
34 | }
35 | t, ok := user.Lookup(targets[0].(*player.Player))
36 | if !ok {
37 | o.Error(lang.Translatef(p.Locale(), "command.target.unknown"))
38 | return
39 | }
40 | if !t.Settings().Privacy.PublicStatistics {
41 | o.Error(lang.Translatef(p.Locale(), "command.stats.private"))
42 | return
43 | }
44 | f, _ := form.NewCasualStats(p, t.Player().XUID())
45 | p.SendForm(f)
46 | }
47 |
48 | // Run ...
49 | func (st StatsOffline) Run(s cmd.Source, o *cmd.Output) {
50 | u, ok := user.Lookup(s.(*player.Player))
51 | if !ok {
52 | // The user somehow left in the middle of this, so just stop in our tracks.
53 | return
54 | }
55 | target, hasTarget := st.Target.Load()
56 | if !hasTarget {
57 | o.Error(lang.Translatef(u.Player().Locale(), "command.target.unknown"))
58 | return
59 | }
60 | t, err := data.LoadOfflineUser(target)
61 | if err != nil {
62 | o.Error(lang.Translatef(u.Player().Locale(), "command.target.unknown"))
63 | return
64 | }
65 | f, err := form.NewCasualStats(u.Player(), t.XUID())
66 | if err != nil {
67 | o.Error(err)
68 | return
69 | }
70 | u.Player().SendForm(f)
71 | }
72 |
73 | // Allow ...
74 | func (Stats) Allow(s cmd.Source) bool {
75 | _, ok := s.(*player.Player)
76 | return ok
77 | }
78 |
79 | // Allow ...
80 | func (StatsOffline) Allow(s cmd.Source) bool {
81 | _, ok := s.(*player.Player)
82 | return ok
83 | }
84 |
--------------------------------------------------------------------------------
/vasar/command/vanish.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/cmd"
5 | "github.com/df-mc/dragonfly/server/player"
6 | "github.com/df-mc/dragonfly/server/world"
7 | "github.com/vasar-network/practice/vasar/user"
8 | "github.com/vasar-network/vails/lang"
9 | "github.com/vasar-network/vails/role"
10 | )
11 |
12 | // vanishGameMode is the game mode used by vanished players.
13 | type vanishGameMode struct{}
14 |
15 | func (vanishGameMode) AllowsEditing() bool { return true }
16 | func (vanishGameMode) AllowsTakingDamage() bool { return false }
17 | func (vanishGameMode) CreativeInventory() bool { return false }
18 | func (vanishGameMode) HasCollision() bool { return false }
19 | func (vanishGameMode) AllowsFlying() bool { return true }
20 | func (vanishGameMode) AllowsInteraction() bool { return true }
21 | func (vanishGameMode) Visible() bool { return true }
22 |
23 | // Vanish is a command that hides a staff from everyone else.
24 | type Vanish struct{}
25 |
26 | // Run ...
27 | func (Vanish) Run(s cmd.Source, o *cmd.Output) {
28 | u, ok := user.Lookup(s.(*player.Player))
29 | if !ok {
30 | // The user somehow left in the middle of this, so just stop in our tracks.
31 | return
32 | }
33 | if u.Vanished() {
34 | user.Alert(s, "staff.alert.vanish.off")
35 | u.Player().SetGameMode(world.GameModeSurvival)
36 | o.Print(lang.Translatef(u.Player().Locale(), "command.vanish.disabled"))
37 | } else {
38 | user.Alert(s, "staff.alert.vanish.on")
39 | u.Player().SetGameMode(vanishGameMode{})
40 | o.Print(lang.Translatef(u.Player().Locale(), "command.vanish.enabled"))
41 | }
42 | for _, t := range user.All() {
43 | if !u.Vanished() {
44 | t.Player().HideEntity(u.Player())
45 | continue
46 | }
47 | t.Player().ShowEntity(u.Player())
48 | }
49 | u.ToggleVanish()
50 | }
51 |
52 | // Allow ...
53 | func (Vanish) Allow(s cmd.Source) bool {
54 | return allow(s, false, role.Trial{})
55 | }
56 |
--------------------------------------------------------------------------------
/vasar/command/variant.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/cmd"
5 | "github.com/df-mc/dragonfly/server/player"
6 | "github.com/vasar-network/practice/vasar/data"
7 | "github.com/vasar-network/practice/vasar/user"
8 | "github.com/vasar-network/vails/lang"
9 | "golang.org/x/exp/slices"
10 | "strings"
11 | )
12 |
13 | // VariantUnlock unlocks a Vasar+ variant for a given user.
14 | type VariantUnlock struct {
15 | Sub unlockVariant
16 | Targets []cmd.Target `cmd:"target"`
17 | Variant variantType `cmd:"variant"`
18 | }
19 |
20 | // Run ...
21 | func (v VariantUnlock) Run(s cmd.Source, o *cmd.Output) {
22 | l := locale(s)
23 | if len(v.Targets) > 1 {
24 | o.Error(lang.Translatef(l, "command.targets.exceed"))
25 | return
26 | }
27 | t, ok := v.Targets[0].(*player.Player)
28 | if !ok {
29 | o.Error(lang.Translatef(l, "command.target.unknown"))
30 | return
31 | }
32 | u, ok := user.Lookup(t)
33 | if !ok {
34 | o.Error(lang.Translatef(l, "command.target.unknown"))
35 | return
36 | }
37 | variant := strings.ToLower(string(v.Variant))
38 | if u.Variant(variant) {
39 | o.Error(lang.Translatef(l, "command.variant.unlocked.already", u.DisplayName(), variant))
40 | return
41 | }
42 | u.UnlockVariant(variant)
43 | o.Print(lang.Translatef(l, "command.variant.unlocked", variant, u.DisplayName()))
44 | }
45 |
46 | // VariantUnlockOffline unlocks a Vasar+ variant for an offline user.
47 | type VariantUnlockOffline struct {
48 | Sub unlockVariant
49 | Target string `cmd:"target"`
50 | Variant variantType `cmd:"variant"`
51 | }
52 |
53 | // Run ...
54 | func (v VariantUnlockOffline) Run(s cmd.Source, o *cmd.Output) {
55 | l := locale(s)
56 | u, err := data.LoadOfflineUser(v.Target)
57 | if err != nil {
58 | o.Error(lang.Translatef(l, "command.target.unknown"))
59 | return
60 | }
61 | variant := strings.ToLower(string(v.Variant))
62 | if slices.Contains(u.Variants, variant) {
63 | o.Error(lang.Translatef(l, "command.variant.unlocked.already", u.DisplayName(), variant))
64 | return
65 | }
66 | u.Variants = append(u.Variants, variant)
67 | _ = data.SaveOfflineUser(u)
68 | o.Print(lang.Translatef(l, "command.variant.unlocked", variant, u.DisplayName()))
69 | }
70 |
71 | // VariantLock locks a Vasar+ variant for a given user.
72 | type VariantLock struct {
73 | Sub lockVariant
74 | Targets []cmd.Target `cmd:"target"`
75 | Variant variantType `cmd:"variant"`
76 | }
77 |
78 | // Run ...
79 | func (v VariantLock) Run(s cmd.Source, o *cmd.Output) {
80 | l := locale(s)
81 | if len(v.Targets) > 1 {
82 | o.Error(lang.Translatef(l, "command.targets.exceed"))
83 | return
84 | }
85 | t, ok := v.Targets[0].(*player.Player)
86 | if !ok {
87 | o.Error(lang.Translatef(l, "command.target.unknown"))
88 | return
89 | }
90 | u, ok := user.Lookup(t)
91 | if !ok {
92 | o.Error(lang.Translatef(l, "command.target.unknown"))
93 | return
94 | }
95 | variant := strings.ToLower(string(v.Variant))
96 | if !u.Variant(variant) {
97 | o.Error(lang.Translatef(l, "command.variant.locked.already", u.DisplayName(), variant))
98 | return
99 | }
100 | u.LockVariant(variant)
101 | o.Print(lang.Translatef(l, "command.variant.locked", variant, u.DisplayName()))
102 | }
103 |
104 | // VariantLockOffline locks a Vasar+ variant for an offline user.
105 | type VariantLockOffline struct {
106 | Sub lockVariant
107 | Target string `cmd:"target"`
108 | Variant variantType `cmd:"variant"`
109 | }
110 |
111 | // Run ...
112 | func (v VariantLockOffline) Run(s cmd.Source, o *cmd.Output) {
113 | l := locale(s)
114 | u, err := data.LoadOfflineUser(v.Target)
115 | if err != nil {
116 | o.Error(lang.Translatef(l, "command.target.unknown"))
117 | return
118 | }
119 | variant := strings.ToLower(string(v.Variant))
120 | if !slices.Contains(u.Variants, variant) {
121 | o.Error(lang.Translatef(l, "command.variant.locked.already", u.DisplayName(), variant))
122 | return
123 | }
124 | ind := slices.Index(u.Variants, variant)
125 | u.Variants = slices.Delete(u.Variants, ind, ind+1)
126 | _ = data.SaveOfflineUser(u)
127 | o.Print(lang.Translatef(l, "command.variant.locked", variant, u.DisplayName()))
128 | }
129 |
130 | // Allow ...
131 | func (VariantUnlock) Allow(s cmd.Source) bool {
132 | return allow(s, true)
133 | }
134 |
135 | // Allow ...
136 | func (VariantUnlockOffline) Allow(s cmd.Source) bool {
137 | return allow(s, true)
138 | }
139 |
140 | // Allow ...
141 | func (VariantLock) Allow(s cmd.Source) bool {
142 | return allow(s, true)
143 | }
144 |
145 | // Allow ...
146 | func (VariantLockOffline) Allow(s cmd.Source) bool {
147 | return allow(s, true)
148 | }
149 |
150 | type (
151 | unlockVariant string
152 | lockVariant string
153 | variantType string
154 | )
155 |
156 | // SubName ...
157 | func (unlockVariant) SubName() string {
158 | return "unlock"
159 | }
160 |
161 | // SubName ...
162 | func (lockVariant) SubName() string {
163 | return "lock"
164 | }
165 |
166 | // Type ...
167 | func (variantType) Type() string {
168 | return "variant"
169 | }
170 |
171 | // Options ...
172 | func (variantType) Options(cmd.Source) []string {
173 | return []string{
174 | "red",
175 | "green",
176 | "blue",
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/vasar/command/whisper.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/cmd"
5 | "github.com/df-mc/dragonfly/server/player"
6 | "github.com/sandertv/gophertunnel/minecraft/text"
7 | "github.com/vasar-network/practice/vasar/user"
8 | "github.com/vasar-network/vails/lang"
9 | "github.com/vasar-network/vails/role"
10 | "strings"
11 | )
12 |
13 | // Whisper is a command that allows a player to send a private message to another player.
14 | type Whisper struct {
15 | Target []cmd.Target `cmd:"target"`
16 | Message cmd.Varargs `cmd:"message"`
17 | }
18 |
19 | // Run ...
20 | func (w Whisper) Run(s cmd.Source, o *cmd.Output) {
21 | l := locale(s)
22 | u, ok := user.Lookup(s.(*player.Player))
23 | if !ok {
24 | // The user somehow left in the middle of this, so just stop in our tracks.
25 | return
26 | }
27 | if !u.Settings().Privacy.PrivateMessages {
28 | o.Error(lang.Translatef(l, "user.whisper.disabled"))
29 | return
30 | }
31 | msg := strings.TrimSpace(string(w.Message))
32 | if len(msg) <= 0 {
33 | o.Error(lang.Translatef(l, "message.empty"))
34 | return
35 | }
36 | if len(w.Target) > 1 {
37 | o.Error(lang.Translatef(l, "command.targets.exceed"))
38 | return
39 | }
40 |
41 | tP, ok := w.Target[0].(*player.Player)
42 | if !ok {
43 | o.Error(lang.Translatef(l, "command.target.unknown"))
44 | return
45 | }
46 | t, ok := user.Lookup(tP)
47 | if !ok {
48 | o.Error(lang.Translatef(l, "command.target.unknown"))
49 | return
50 | }
51 | if !t.Settings().Privacy.PrivateMessages {
52 | o.Error(lang.Translatef(l, "target.whisper.disabled"))
53 | return
54 | }
55 |
56 | uTag, uMsg := text.Colourf("%s", u.DisplayName()), text.Colourf("%s", msg)
57 | tTag, tMsg := text.Colourf("%s", t.DisplayName()), text.Colourf("%s", msg)
58 | if _, ok := u.Roles().Highest().(role.Default); !ok {
59 | uMsg = t.Roles().Highest().Tag(msg)
60 | uTag = u.Roles().Highest().Tag(u.DisplayName())
61 | }
62 | if _, ok := t.Roles().Highest().(role.Default); !ok {
63 | tMsg = u.Roles().Highest().Tag(msg)
64 | tTag = t.Roles().Highest().Tag(t.DisplayName())
65 | }
66 |
67 | t.SetLastMessageFrom(u.Player())
68 | t.SendCustomSound("random.orb", 1, 1, false)
69 | u.Message("command.whisper.to", tTag, tMsg)
70 | t.Message("command.whisper.from", uTag, uMsg)
71 | }
72 |
73 | // Allow ...
74 | func (Whisper) Allow(s cmd.Source) bool {
75 | _, ok := s.(*player.Player)
76 | return ok
77 | }
78 |
--------------------------------------------------------------------------------
/vasar/command/who.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/cmd"
5 | "github.com/df-mc/dragonfly/server/player"
6 | "github.com/vasar-network/practice/vasar/user"
7 | "github.com/vasar-network/vails/lang"
8 | "github.com/vasar-network/vails/role"
9 | )
10 |
11 | // Who is a command that displays information about a specific player.
12 | type Who struct {
13 | Targets []cmd.Target `cmd:"target"`
14 | }
15 |
16 | // Run ...
17 | func (w Who) Run(s cmd.Source, o *cmd.Output) {
18 | l := locale(s)
19 | if len(w.Targets) > 1 {
20 | o.Error(lang.Translatef(l, "command.targets.exceed"))
21 | return
22 | }
23 | if target, ok := w.Targets[0].(*player.Player); ok {
24 | op := true
25 | if p, ok := s.(*player.Player); ok {
26 | if u, ok := user.Lookup(p); ok {
27 | op = u.Roles().Contains(role.Operator{})
28 | }
29 | }
30 |
31 | t, ok := user.Lookup(target)
32 | if !ok {
33 | o.Error(lang.Translatef(l, "command.target.unknown"))
34 | return
35 | }
36 | if op {
37 | o.Print(lang.Translatef(l, "command.who.op",
38 | t.Player().Name(),
39 | t.Device(),
40 | t.DeviceGroup(),
41 | t.Latency(),
42 | t.DeviceID(),
43 | t.SelfSignedID(),
44 | ))
45 | } else {
46 | o.Print(lang.Translatef(l, "command.who.staff",
47 | t.Player().Name(),
48 | t.Device(),
49 | t.DeviceGroup(),
50 | t.Latency(),
51 | ))
52 | }
53 | }
54 | }
55 |
56 | // Allow ...
57 | func (Who) Allow(s cmd.Source) bool {
58 | return allow(s, true, role.Mod{})
59 | }
60 |
--------------------------------------------------------------------------------
/vasar/config.go:
--------------------------------------------------------------------------------
1 | package vasar
2 |
3 | import "github.com/df-mc/dragonfly/server"
4 |
5 | // Config is an extension of the Dragonfly server config to include fields specific to Vasar.
6 | type Config struct {
7 | server.Config
8 | // Vasar contains fields specific to Vasar.
9 | Vasar struct {
10 | // Tebex is the Tebex API key.
11 | Tebex string
12 | // Whitelisted is true if the server is whitelisted.
13 | Whitelisted bool
14 | // Season is the current season of the server.
15 | Season int
16 | // Start is the date the season started.
17 | Start string
18 | // End is the date the season ends.
19 | End string
20 | }
21 | // Pack contains fields related to the pack.
22 | Pack struct {
23 | // Key is the pack encryption key.
24 | Key string
25 | // Path is the path to the pack.
26 | Path string
27 | }
28 | // Oomph contains fields specific to Oomph.
29 | Oomph struct {
30 | // Address is the address to run Oomph on.
31 | Address string
32 | }
33 | // Sentry contains fields used for Sentry.
34 | Sentry struct {
35 | // Release is the release name.
36 | Release string
37 | // Dsn is the Sentry Dsn.
38 | Dsn string
39 | }
40 | }
41 |
42 | // DefaultConfig returns a default config for the server.
43 | func DefaultConfig() Config {
44 | c := Config{}
45 | c.Config = server.DefaultConfig()
46 | c.Vasar.Whitelisted = true
47 | c.Vasar.Season = 1
48 | c.Vasar.Start = "Edit this!"
49 | c.Vasar.End = "Edit this!"
50 | return c
51 | }
52 |
--------------------------------------------------------------------------------
/vasar/data/data.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "github.com/upper/db/v4"
7 | "github.com/upper/db/v4/adapter/mongo"
8 | "github.com/vasar-network/practice/vasar/user"
9 | "os"
10 | "time"
11 | )
12 |
13 | // userData is the data structure that is used to store the user data in the database.
14 | type userData struct {
15 | // XUID is the XUID of the user.
16 | XUID string `bson:"xuid"`
17 | // Name is the last username of the user.
18 | Name string `bson:"name"`
19 | // DisplayName is the name displayed to other users.
20 | DisplayName string `bson:"display_name"`
21 | // DeviceID is the device ID of the last device the user logged in from.
22 | DeviceID string `bson:"did"`
23 | // SelfSignedID is the self-signed ID of the last client session of the user.
24 | SelfSignedID string `bson:"ssid"`
25 | // Address is the hashed IP address of the user.
26 | Address string `bson:"address"`
27 | // Whitelisted is true if the user is whitelisted.
28 | Whitelisted bool `bson:"whitelisted"`
29 | // FirstLogin is the time the user first logged in.
30 | FirstLogin time.Time `bson:"first_login"`
31 | // PlayTime is the duration the user has played Vasar Practice for.
32 | PlayTime time.Duration `bson:"playtime"`
33 |
34 | // Variants is a list of Vasar+ variants the user has unlocked.
35 | Variants []string `bson:"variants"`
36 | // Roles is a list of roles that the user has.
37 | Roles []roleData `bson:"roles"`
38 | // Punishments is a list of active punishments that the user has.
39 | Punishments punishmentData `bson:"punishments"`
40 |
41 | // Settings is a list of settings that the user has.
42 | Settings user.Settings `bson:"settings"`
43 | // Practice is a list of user statistics specific to Vasar Practice.
44 | Practice user.Stats `bson:"practice"`
45 | }
46 |
47 | // roleData is the data structure that is used to store roles in the database.
48 | type roleData struct {
49 | // Name is the name of the role.
50 | Name string `bson:"name"`
51 | // Expires is true if the role expires.
52 | Expires bool `bson:"expires"`
53 | // Expiration is the expiration time of the role.
54 | Expiration time.Time `bson:"expiration"`
55 | }
56 |
57 | // punishmentData is the data structure that is used to store punishments in the database.
58 | type punishmentData struct {
59 | // Mute contains punishment data on the user's mute.
60 | Mute user.Punishment `bson:"mute"`
61 | // Ban contains punishment data on the user's ban.
62 | Ban user.Punishment `bson:"ban"`
63 | }
64 |
65 | // salt contains the salt that starts with "THIS NIGGA GOT A FAT DICK" used for hashing. Please don't judge.
66 | const salt = "THIS NIGGA GOT A FAT DICK I LOVE BIG DICKS YEAH UH, PUSSY ROLLLLLLLLIN, I LIKE BIG DICKS!!! PUSSY ROLLLLLLLIN OH YEAH WHATS GOOD NEEGUS!!!! BIG FAT COCK AND SHE SUCK WHEN I SAY SO TWO BIG TITTIES AND A BIG ASS SEX-SO THREE MORE CUM DROPS ALL ON HER ASS-HOLE, PULL UP THIS DICK THATS A BIG FUCKING PUSSY HOLE YEAHHHHHHHHHHHH!"
67 |
68 | // sess is the Upper database session.
69 | var sess db.Session
70 |
71 | // init creates the Upper database connection.
72 | func init() {
73 | path := os.Getenv("VASAR_DB")
74 | if len(path) == 0 {
75 | panic("vasar: mongo environment variable is not set")
76 | }
77 |
78 | b, err := os.ReadFile(path)
79 | if err != nil {
80 | panic(fmt.Sprintf("vasar: %s", err))
81 | }
82 |
83 | var settings mongo.ConnectionURL
84 | err = json.Unmarshal(b, &settings)
85 | if err != nil {
86 | panic(fmt.Sprintf("vasr: %s", err))
87 | }
88 |
89 | sess, err = mongo.Open(settings)
90 | if err != nil {
91 | panic(fmt.Sprintf("failed to start mongo connection: %v", err))
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/vasar/data/online.go:
--------------------------------------------------------------------------------
1 | package data
2 |
3 | import (
4 | "encoding/hex"
5 | "fmt"
6 | "github.com/df-mc/dragonfly/server/player"
7 | "github.com/upper/db/v4"
8 | "github.com/vasar-network/practice/vasar/user"
9 | "github.com/vasar-network/vails"
10 | "github.com/vasar-network/vails/role"
11 | "golang.org/x/crypto/sha3"
12 | "net/netip"
13 | "strings"
14 | "time"
15 | )
16 |
17 | // LoadUser loads a *user.User from the database using a *player.Player. If the user does not exist, it will be created.
18 | func LoadUser(p *player.Player) (*user.User, error) {
19 | result := sess.Collection("users").Find(db.Or(db.Cond{"name": strings.ToLower(p.Name())}, db.Cond{"xuid": p.XUID()}))
20 | addr, _ := netip.ParseAddrPort(p.Addr().String())
21 |
22 | s := sha3.New256()
23 | s.Write(addr.Addr().AsSlice())
24 | s.Write([]byte(salt))
25 | address := hex.EncodeToString(s.Sum(nil))
26 |
27 | if ok, _ := result.Exists(); !ok {
28 | return user.NewUser(p,
29 | user.NewRoles([]vails.Role{role.Default{}}, map[vails.Role]time.Time{}),
30 | user.DefaultSettings(),
31 | user.DefaultStats(),
32 | time.Now(),
33 | 0,
34 | address,
35 | false,
36 | []string{},
37 | user.Punishment{},
38 | user.Punishment{},
39 | ), nil
40 | }
41 |
42 | var data userData
43 | if err := result.One(&data); err != nil {
44 | return nil, fmt.Errorf("load user: %v", err)
45 | }
46 |
47 | var roles []vails.Role
48 | expirations := make(map[vails.Role]time.Time)
49 | for _, dat := range data.Roles {
50 | if dat.Expires && time.Now().After(dat.Expiration) {
51 | continue
52 | }
53 | r, ok := role.ByName(dat.Name)
54 | if !ok {
55 | return nil, fmt.Errorf("load user: role %s does not exist", dat.Name)
56 | }
57 | roles = append(roles, r)
58 | if dat.Expires {
59 | expirations[r] = dat.Expiration
60 | }
61 | }
62 | return user.NewUser(p, user.NewRoles(roles, expirations), data.Settings, data.Practice, data.FirstLogin, data.PlayTime, address, data.Whitelisted, data.Variants, data.Punishments.Mute, data.Punishments.Ban), nil
63 | }
64 |
65 | // SaveUser saves a *user.User to the database. If an error occurs, it will be returned to the second return value.
66 | func SaveUser(u *user.User) error {
67 | var roles []roleData
68 | for _, r := range u.Roles().All() {
69 | data := roleData{Name: r.Name()}
70 | if e, ok := u.Roles().Expiration(r); ok {
71 | data.Expiration, data.Expires = e, true
72 | }
73 | roles = append(roles, data)
74 | }
75 |
76 | p := u.Player()
77 | users := sess.Collection("users")
78 | m, _ := u.Mute()
79 | data := userData{
80 | XUID: p.XUID(),
81 | DisplayName: p.Name(),
82 | Name: strings.ToLower(p.Name()),
83 | DeviceID: u.DeviceID(),
84 | SelfSignedID: u.SelfSignedID(),
85 | Address: u.HashedAddress(),
86 | Whitelisted: u.Whitelisted(),
87 |
88 | FirstLogin: u.FirstLogin(),
89 | PlayTime: u.PlayTime(),
90 |
91 | Settings: u.Settings(),
92 | Practice: u.Stats(),
93 |
94 | Variants: u.Variants(),
95 | Roles: roles,
96 | Punishments: punishmentData{
97 | Mute: m,
98 | Ban: u.Ban(),
99 | },
100 | }
101 |
102 | entry := users.Find(db.Or(db.Cond{"name": strings.ToLower(p.Name())}, db.Cond{"xuid": p.XUID()}))
103 | if ok, _ := entry.Exists(); ok {
104 | return entry.Update(data)
105 | }
106 | _, err := users.Insert(data)
107 | return err
108 | }
109 |
--------------------------------------------------------------------------------
/vasar/entity/fishing_hook.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/block/cube"
5 | "github.com/df-mc/dragonfly/server/block/cube/trace"
6 | "github.com/df-mc/dragonfly/server/entity"
7 | "github.com/df-mc/dragonfly/server/entity/damage"
8 | "github.com/df-mc/dragonfly/server/player"
9 | "github.com/df-mc/dragonfly/server/world"
10 | "github.com/go-gl/mathgl/mgl64"
11 | "math/rand"
12 | )
13 |
14 | // FishingHook ...
15 | type FishingHook struct {
16 | transform
17 |
18 | age int
19 |
20 | owner *player.Player
21 |
22 | close bool
23 |
24 | c *entity.ProjectileComputer
25 | }
26 |
27 | // NewFishingHook ...
28 | func NewFishingHook(pos, vel mgl64.Vec3, owner *player.Player) *FishingHook {
29 | f := &FishingHook{
30 | owner: owner,
31 | c: &entity.ProjectileComputer{MovementComputer: &entity.MovementComputer{
32 | Gravity: 0.1,
33 | Drag: 0.02,
34 | DragBeforeGravity: true,
35 | }},
36 | }
37 | f.transform = newTransform(f, pos)
38 | f.vel = vel.Normalize().Add(mgl64.Vec3{
39 | rand.Float64(),
40 | rand.Float64(),
41 | rand.Float64(),
42 | }.Mul(0.007499999832361937)).Mul(1.3)
43 | f.vel[0] += vel[0]
44 | f.vel[2] += vel[2]
45 | return f
46 | }
47 |
48 | // Tick ...
49 | func (f *FishingHook) Tick(w *world.World, current int64) {
50 | if f.close {
51 | _ = f.Close()
52 | return
53 | }
54 |
55 | held, _ := f.owner.HeldItems()
56 | if r, ok := held.Item().(interface {
57 | Rod() bool
58 | }); !ok || !r.Rod() {
59 | f.close = true
60 | return
61 | }
62 |
63 | f.mu.Lock()
64 | vel := f.vel
65 | m, result := f.c.TickMovement(f, f.pos, f.vel, 0, 0, f.ignores)
66 | f.pos, f.vel = m.Position(), m.Velocity()
67 |
68 | f.age++
69 | f.mu.Unlock()
70 |
71 | m.Send()
72 |
73 | if m.Position()[1] < float64(w.Range()[0]) && current%10 == 0 {
74 | f.close = true
75 | return
76 | }
77 |
78 | if result != nil {
79 | if res, ok := result.(trace.EntityResult); ok {
80 | if l, ok := res.Entity().(entity.Living); ok && !l.AttackImmune() {
81 | if _, vulnerable := l.Hurt(0.0, damage.SourceProjectile{Projectile: f, Owner: f.Owner()}); vulnerable {
82 | if entity.DirectionVector(f.owner).Dot(entity.DirectionVector(l)) > 0 {
83 | // Pull back the target.
84 | l.KnockBack(l.Position().Add(vel), 0.230, 0.372)
85 | } else {
86 | // Push back the target.
87 | l.KnockBack(l.Position().Sub(vel), 0.374, 0.372)
88 | }
89 | }
90 | }
91 | }
92 | f.close = true
93 | }
94 | }
95 |
96 | // Name ...
97 | func (f *FishingHook) Name() string {
98 | return "Fishing Hook"
99 | }
100 |
101 | // EncodeEntity ...
102 | func (f *FishingHook) EncodeEntity() string {
103 | return "minecraft:fishing_hook"
104 | }
105 |
106 | // BBox ...
107 | func (f *FishingHook) BBox() cube.BBox {
108 | return cube.Box(-0.125, 0, -0.125, 0.125, 0.25, 0.125)
109 | }
110 |
111 | // Owner ...
112 | func (f *FishingHook) Owner() world.Entity {
113 | return f.owner
114 | }
115 |
116 | // ignores returns whether the arrow should ignore collision with the entity passed.
117 | func (f *FishingHook) ignores(e world.Entity) bool {
118 | _, ok := e.(entity.Living)
119 | g, ok2 := e.(interface{ GameMode() world.GameMode })
120 | return !ok || e == f || (f.age < 5 && e == f.owner) || (ok2 && !g.GameMode().AllowsInteraction())
121 | }
122 |
--------------------------------------------------------------------------------
/vasar/entity/lightning.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | import (
4 | "github.com/df-mc/atomic"
5 | "github.com/df-mc/dragonfly/server/block/cube"
6 | "github.com/df-mc/dragonfly/server/world"
7 | "github.com/go-gl/mathgl/mgl64"
8 | )
9 |
10 | // Lightning is a lethal element to thunderstorms. Lightning momentarily increases the skylight's brightness to slightly
11 | // greater than full daylight.
12 | type Lightning struct {
13 | pos atomic.Value[mgl64.Vec3]
14 | }
15 |
16 | // NewLightning creates a lightning entity. The lightning entity will be positioned at the position passed.
17 | func NewLightning(pos mgl64.Vec3) *Lightning {
18 | return &Lightning{
19 | pos: *atomic.NewValue(pos),
20 | }
21 | }
22 |
23 | // Position returns the current position of the lightning entity.
24 | func (l *Lightning) Position() mgl64.Vec3 {
25 | return l.pos.Load()
26 | }
27 |
28 | // World returns the world that the lightning entity is currently in, or nil if it is not added to a world.
29 | func (l *Lightning) World() *world.World {
30 | w, _ := world.OfEntity(l)
31 | return w
32 | }
33 |
34 | // BBox ...
35 | func (*Lightning) BBox() cube.BBox {
36 | return cube.Box(0, 0, 0, 0, 0, 0)
37 | }
38 |
39 | // Close closes the lighting.
40 | func (l *Lightning) Close() error {
41 | l.World().RemoveEntity(l)
42 | return nil
43 | }
44 |
45 | // OnGround ...
46 | func (*Lightning) OnGround() bool {
47 | return false
48 | }
49 |
50 | // Rotation ...
51 | func (*Lightning) Rotation() (yaw, pitch float64) {
52 | return 0, 0
53 | }
54 |
55 | // EncodeEntity ...
56 | func (*Lightning) EncodeEntity() string {
57 | return "minecraft:lightning_bolt"
58 | }
59 |
60 | // Name ...
61 | func (*Lightning) Name() string {
62 | return "Lightning Bolt"
63 | }
64 |
--------------------------------------------------------------------------------
/vasar/entity/transform.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/world"
5 | "github.com/go-gl/mathgl/mgl64"
6 | "sync"
7 | )
8 |
9 | // transform holds the base position and velocity of an entity. It holds several methods which can be used when
10 | // embedding the struct.
11 | type transform struct {
12 | e world.Entity
13 | mu sync.Mutex
14 | vel, pos mgl64.Vec3
15 | }
16 |
17 | // newTransform creates a new transform to embed for the world.Entity passed.
18 | func newTransform(e world.Entity, pos mgl64.Vec3) transform {
19 | return transform{e: e, pos: pos}
20 | }
21 |
22 | // Position returns the current position of the entity.
23 | func (t *transform) Position() mgl64.Vec3 {
24 | t.mu.Lock()
25 | defer t.mu.Unlock()
26 | return t.pos
27 | }
28 |
29 | // Velocity returns the current velocity of the entity. The values in the Vec3 returned represent the speed on
30 | // that axis in blocks/tick.
31 | func (t *transform) Velocity() mgl64.Vec3 {
32 | t.mu.Lock()
33 | defer t.mu.Unlock()
34 | return t.vel
35 | }
36 |
37 | // SetVelocity sets the velocity of the entity. The values in the Vec3 passed represent the speed on
38 | // that axis in blocks/tick.
39 | func (t *transform) SetVelocity(v mgl64.Vec3) {
40 | t.mu.Lock()
41 | defer t.mu.Unlock()
42 | t.vel = v
43 | }
44 |
45 | // Rotation always returns 0.
46 | func (t *transform) Rotation() (float64, float64) { return 0, 0 }
47 |
48 | // World returns the world of the entity.
49 | func (t *transform) World() *world.World {
50 | w, _ := world.OfEntity(t.e)
51 | return w
52 | }
53 |
54 | // Close closes the transform and removes the associated entity from the world.
55 | func (t *transform) Close() error {
56 | w, _ := world.OfEntity(t.e)
57 | w.RemoveEntity(t.e)
58 | return nil
59 | }
60 |
--------------------------------------------------------------------------------
/vasar/entity/vasar_pearl.go:
--------------------------------------------------------------------------------
1 | package entity
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/block/cube"
5 | "github.com/df-mc/dragonfly/server/block/cube/trace"
6 | "github.com/df-mc/dragonfly/server/entity"
7 | "github.com/df-mc/dragonfly/server/entity/damage"
8 | "github.com/df-mc/dragonfly/server/event"
9 | "github.com/df-mc/dragonfly/server/player"
10 | "github.com/df-mc/dragonfly/server/session"
11 | "github.com/df-mc/dragonfly/server/world"
12 | "github.com/df-mc/dragonfly/server/world/particle"
13 | "github.com/df-mc/dragonfly/server/world/sound"
14 | "github.com/go-gl/mathgl/mgl64"
15 | "time"
16 | _ "unsafe"
17 | )
18 |
19 | // PearlHandler ...
20 | type PearlHandler interface {
21 | // HandleTeleport ...
22 | HandleTeleport(*event.Context, *player.Player, mgl64.Vec3)
23 | }
24 |
25 | // NopPearlHandler ...
26 | type NopPearlHandler struct{}
27 |
28 | func (NopPearlHandler) HandleTeleport(*event.Context, *player.Player, mgl64.Vec3) {}
29 |
30 | // VasarPearl is a copy of an ender pearl with some edits.
31 | type VasarPearl struct {
32 | transform
33 | yaw, pitch float64
34 |
35 | age int
36 | close bool
37 | h PearlHandler
38 |
39 | owner world.Entity
40 |
41 | c *entity.ProjectileComputer
42 | }
43 |
44 | // NewEnderPearl ...
45 | func NewEnderPearl(pos, vel mgl64.Vec3, yaw, pitch float64, owner world.Entity) *VasarPearl {
46 | e := &VasarPearl{
47 | yaw: yaw,
48 | pitch: pitch,
49 | c: &entity.ProjectileComputer{MovementComputer: &entity.MovementComputer{
50 | Gravity: 0.065,
51 | Drag: 0.0025,
52 | DragBeforeGravity: true,
53 | }},
54 | owner: owner,
55 | h: NopPearlHandler{},
56 | }
57 | e.transform = newTransform(e, pos)
58 | e.vel = vel
59 | return e
60 | }
61 |
62 | // Name ...
63 | func (e *VasarPearl) Name() string {
64 | return "Ender Pearl"
65 | }
66 |
67 | // EncodeEntity ...
68 | func (e *VasarPearl) EncodeEntity() string {
69 | return "minecraft:ender_pearl"
70 | }
71 |
72 | // Scale ...
73 | func (e *VasarPearl) Scale() float64 {
74 | return 0.575
75 | }
76 |
77 | // BBox ...
78 | func (e *VasarPearl) BBox() cube.BBox {
79 | return cube.Box(-0.125, 0, -0.125, 0.125, 0.25, 0.125)
80 | }
81 |
82 | // Rotation ...
83 | func (e *VasarPearl) Rotation() (float64, float64) {
84 | e.mu.Lock()
85 | defer e.mu.Unlock()
86 | return e.yaw, e.pitch
87 | }
88 |
89 | // Tick ...
90 | func (e *VasarPearl) Tick(w *world.World, current int64) {
91 | if e.close {
92 | _ = e.Close()
93 | return
94 | }
95 | e.mu.Lock()
96 | m, result := e.c.TickMovement(e, e.pos, e.vel, e.yaw, e.pitch, e.ignores)
97 | e.yaw, e.pitch = m.Rotation()
98 | e.pos, e.vel = m.Position(), m.Velocity()
99 | h := e.h
100 | e.mu.Unlock()
101 |
102 | e.age++
103 | m.Send()
104 |
105 | if m.Position()[1] < float64(w.Range()[0]) && current%10 == 0 {
106 | e.close = true
107 | return
108 | }
109 |
110 | if result != nil {
111 | var isEntity bool
112 | if r, ok := result.(trace.EntityResult); ok {
113 | if l, ok := r.Entity().(entity.Living); ok {
114 | isEntity = ok
115 | if _, vulnerable := l.Hurt(0.0, damage.SourceProjectile{Projectile: e, Owner: e.Owner()}); vulnerable {
116 | l.KnockBack(m.Position(), 0.435, 0.355)
117 | }
118 | }
119 | }
120 |
121 | if owner := e.Owner(); owner != nil {
122 | if p, ok := owner.(*player.Player); ok {
123 | pos := p.Position()
124 | w.PlaySound(pos, sound.Teleport{})
125 |
126 | ctx := event.C()
127 | h.HandleTeleport(ctx, p, m.Position())
128 | if !ctx.Cancelled() {
129 | session_ViewEntityTeleport(player_session(p), owner, m.Position())
130 | p.Move(m.Position().Sub(pos), 0, 0)
131 | }
132 |
133 | w.AddParticle(m.Position(), particle.EndermanTeleportParticle{})
134 | w.PlaySound(m.Position(), sound.Teleport{})
135 |
136 | p.Hurt(0, damage.SourceFall{})
137 |
138 | if isEntity {
139 | p.SetAttackImmunity(245 * time.Millisecond)
140 | }
141 | }
142 | }
143 |
144 | e.close = true
145 | }
146 | }
147 |
148 | // ignores returns whether the ender pearl should ignore collision with the entity passed.
149 | func (e *VasarPearl) ignores(otherEntity world.Entity) bool {
150 | _, ok := otherEntity.(entity.Living)
151 | g, ok2 := otherEntity.(interface{ GameMode() world.GameMode })
152 | return !ok || otherEntity == e || (e.age < 5 && otherEntity == e.owner) || (ok2 && !g.GameMode().AllowsInteraction())
153 | }
154 |
155 | // Handle ...
156 | func (e *VasarPearl) Handle(h PearlHandler) {
157 | e.mu.Lock()
158 | defer e.mu.Unlock()
159 | e.h = h
160 | }
161 |
162 | // Owner ...
163 | func (e *VasarPearl) Owner() world.Entity {
164 | e.mu.Lock()
165 | defer e.mu.Unlock()
166 | return e.owner
167 | }
168 |
169 | // Own ...
170 | func (e *VasarPearl) Own(owner world.Entity) {
171 | e.mu.Lock()
172 | defer e.mu.Unlock()
173 | e.owner = owner
174 | }
175 |
176 | //go:linkname player_session github.com/df-mc/dragonfly/server/player.(*Player).session
177 | //noinspection ALL
178 | func player_session(*player.Player) *session.Session
179 |
180 | //go:linkname session_ViewEntityTeleport github.com/df-mc/dragonfly/server/session.(*Session).ViewEntityTeleport
181 | //noinspection ALL
182 | func session_ViewEntityTeleport(*session.Session, world.Entity, mgl64.Vec3)
183 |
--------------------------------------------------------------------------------
/vasar/form/ban.go:
--------------------------------------------------------------------------------
1 | package form
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/player"
5 | "github.com/df-mc/dragonfly/server/player/form"
6 | "github.com/hako/durafmt"
7 | "github.com/unickorn/strcenter"
8 | "github.com/vasar-network/practice/vasar/data"
9 | "github.com/vasar-network/practice/vasar/user"
10 | "github.com/vasar-network/vails/lang"
11 | "github.com/vasar-network/vails/role"
12 | "github.com/vasar-network/vails/webhook"
13 | "golang.org/x/exp/maps"
14 | "math/rand"
15 | "sort"
16 | "strings"
17 | "time"
18 | )
19 |
20 | // ban is a form that allows a user to issue a ban.
21 | type ban struct {
22 | // Reason is a dropdown that allows a user to select a ban reason.
23 | Reason form.Dropdown
24 | // OnlinePlayer is a dropdown that allows a user to select an online player.
25 | OnlinePlayer form.Dropdown
26 | // OfflinePlayer is an input field that allows a user to enter an offline player.
27 | OfflinePlayer form.Input
28 | // online is a list of online players' XUIDs indexed by their names.
29 | online map[string]string
30 | }
31 |
32 | // NewBan creates a new form to issue a ban.
33 | func NewBan() form.Form {
34 | online := make(map[string]string)
35 | for _, u := range user.All() {
36 | online[u.Player().Name()] = u.Player().XUID()
37 | }
38 | names := [...]string{"Steve Harvey", "Elon Musk", "Bill Gates", "Mark Zuckerberg", "Jeff Bezos", "Warren Buffet", "Larry Page", "Sergey Brin", "Larry Ellison", "Tim Cook", "Steve Ballmer", "Daniel Larson", "Steve"}
39 | list := maps.Keys(online)
40 | sort.Strings(list)
41 | return form.New(ban{
42 | Reason: form.NewDropdown("Reason", []string{"Unfair Advantage", "Unfair Advantage in Ranked", "Interference", "Exploitation", "Permission Abuse", "Invalid Skin", "Evasion", "Advertising"}, 0),
43 | OnlinePlayer: form.NewDropdown("Online Player", list, 0),
44 | OfflinePlayer: form.NewInput("Offline Player", "", names[rand.Intn(len(names)-1)]),
45 | online: online,
46 | }, "Ban")
47 | }
48 |
49 | // Submit ...
50 | func (b ban) Submit(s form.Submitter) {
51 | p := s.(*player.Player)
52 | u, ok := user.Lookup(p)
53 | if !ok {
54 | // User somehow left midway through the form.
55 | return
56 | }
57 | if !u.Roles().Contains(role.Trial{}, role.Operator{}) {
58 | // In case the user's role was removed while the form was open.
59 | return
60 | }
61 | var length time.Duration
62 | reason := b.Reason.Options[b.Reason.Value()]
63 | switch reason {
64 | case "Unfair Advantage":
65 | length = time.Hour * 24 * 30
66 | case "Unfair Advantage in Ranked":
67 | length = time.Hour * 24 * 90
68 | case "Interference":
69 | length = time.Hour * 12
70 | case "Exploitation":
71 | length = time.Hour * 24 * 9
72 | case "Permission Abuse":
73 | length = time.Hour * 24 * 30
74 | case "Invalid Skin":
75 | length = time.Hour * 24 * 3
76 | case "Evasion":
77 | length = time.Hour * 24 * 120
78 | case "Advertising":
79 | length = time.Hour * 24 * 6
80 | }
81 |
82 | punishment := user.Punishment{
83 | Staff: p.Name(),
84 | Reason: reason,
85 | Occurrence: time.Now(),
86 | Expiration: time.Now().Add(length),
87 | }
88 | var name string
89 | if offlineName := strings.TrimSpace(b.OfflinePlayer.Value()); offlineName != "" {
90 | if strings.EqualFold(offlineName, p.Name()) {
91 | u.Message("command.ban.self")
92 | return
93 | }
94 | t, err := data.LoadOfflineUser(offlineName)
95 | if err != nil {
96 | u.Message("command.target.unknown")
97 | return
98 | }
99 | if t.Roles.Contains(role.Operator{}) {
100 | u.Message("command.ban.operator")
101 | return
102 | }
103 | if !t.Ban.Expired() {
104 | u.Message("command.ban.already")
105 | return
106 | }
107 | t.Ban = punishment
108 | _ = data.SaveOfflineUser(t)
109 | name = t.DisplayName()
110 | } else {
111 | t, ok := user.LookupXUID(b.online[b.OnlinePlayer.Options[b.OnlinePlayer.Value()]])
112 | if !ok {
113 | u.Message("command.target.unknown")
114 | return
115 | }
116 | if t.Roles().Contains(role.Operator{}) {
117 | u.Message("command.ban.operator`")
118 | return
119 | }
120 |
121 | tP := t.Player()
122 | t.SetBan(punishment)
123 | tP.Disconnect(strcenter.CenterLine(strings.Join([]string{
124 | lang.Translatef(tP.Locale(), "user.ban.header"),
125 | lang.Translatef(tP.Locale(), "user.ban.description", reason, durafmt.ParseShort(length)),
126 | }, "\n")))
127 | name = t.Player().Name()
128 | }
129 |
130 | user.Alert(p, "staff.alert.ban", name, reason)
131 | user.Broadcast("command.ban.broadcast", p.Name(), name, reason)
132 | webhook.SendPunishment(p.Name(), name, reason, "Ban")
133 | u.Message("command.ban.success", name, reason)
134 | }
135 |
--------------------------------------------------------------------------------
/vasar/form/blacklist.go:
--------------------------------------------------------------------------------
1 | package form
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/player"
5 | "github.com/df-mc/dragonfly/server/player/form"
6 | "github.com/unickorn/strcenter"
7 | "github.com/vasar-network/practice/vasar/data"
8 | "github.com/vasar-network/practice/vasar/user"
9 | "github.com/vasar-network/vails/lang"
10 | "github.com/vasar-network/vails/role"
11 | "github.com/vasar-network/vails/webhook"
12 | "golang.org/x/exp/maps"
13 | "math/rand"
14 | "sort"
15 | "strings"
16 | "time"
17 | )
18 |
19 | // blacklist is a form that allows a user to issue a blacklist.
20 | type blacklist struct {
21 | // Reason is a dropdown that allows a user to select a blacklist reason.
22 | Reason form.Input
23 | // OnlinePlayer is a dropdown that allows a user to select an online player.
24 | OnlinePlayer form.Dropdown
25 | // OfflinePlayer is an input field that allows a user to enter an offline player.
26 | OfflinePlayer form.Input
27 | // online is a list of online players' XUIDs indexed by their names.
28 | online map[string]string
29 | }
30 |
31 | // NewBlacklist creates a new form to issue a blacklist.
32 | func NewBlacklist() form.Form {
33 | online := make(map[string]string)
34 | for _, u := range user.All() {
35 | online[u.Player().Name()] = u.Player().XUID()
36 | }
37 | names := [...]string{"Steve Harvey", "Elon Musk", "Bill Gates", "Mark Zuckerberg", "Jeff Bezos", "Warren Buffet", "Larry Page", "Sergey Brin", "Larry Ellison", "Tim Cook", "Steve Ballmer", "Daniel Larson", "Steve"}
38 | list := maps.Keys(online)
39 | sort.Strings(list)
40 | return form.New(blacklist{
41 | Reason: form.NewInput("Reason", "", "Enter a reason for the blacklist."),
42 | OnlinePlayer: form.NewDropdown("Online Player", list, 0),
43 | OfflinePlayer: form.NewInput("Offline Player", "", names[rand.Intn(len(names)-1)]),
44 | online: online,
45 | }, "Blacklist")
46 | }
47 |
48 | // Submit ...
49 | func (b blacklist) Submit(s form.Submitter) {
50 | p := s.(*player.Player)
51 | u, ok := user.Lookup(p)
52 | if !ok {
53 | // User somehow left midway through the form.
54 | return
55 | }
56 | if !u.Roles().Contains(role.Manager{}, role.Operator{}) {
57 | // In case the user's role was removed while the form was open.
58 | return
59 | }
60 | reason := strings.TrimSpace(b.Reason.Value())
61 | if len(reason) == 0 {
62 | reason = "None"
63 | }
64 |
65 | punishment := user.Punishment{
66 | Staff: p.Name(),
67 | Reason: reason,
68 | Occurrence: time.Now(),
69 | Permanent: true,
70 | }
71 | var name string
72 | if offlineName := strings.TrimSpace(b.OfflinePlayer.Value()); offlineName != "" {
73 | if strings.EqualFold(offlineName, p.Name()) {
74 | u.Message("command.blacklist.self")
75 | return
76 | }
77 | t, err := data.LoadOfflineUser(offlineName)
78 | if err != nil {
79 | u.Message("command.target.unknown")
80 | return
81 | }
82 | if t.Roles.Contains(role.Operator{}) {
83 | u.Message("command.blacklist.operator")
84 | return
85 | }
86 | if !t.Ban.Expired() && t.Ban.Permanent {
87 | u.Message("command.blacklist.already")
88 | return
89 | }
90 | t.Ban = punishment
91 | name = t.DisplayName()
92 | _ = data.SaveOfflineUser(t)
93 | } else {
94 | t, ok := user.LookupXUID(b.online[b.OnlinePlayer.Options[b.OnlinePlayer.Value()]])
95 | if !ok {
96 | u.Message("command.target.unknown")
97 | return
98 | }
99 | if t.Roles().Contains(role.Operator{}) {
100 | u.Message("command.blacklist.operator")
101 | return
102 | }
103 |
104 | tP := t.Player()
105 | t.SetBan(punishment)
106 | tP.Disconnect(strcenter.CenterLine(strings.Join([]string{
107 | lang.Translatef(tP.Locale(), "user.blacklist.header"),
108 | lang.Translatef(tP.Locale(), "user.blacklist.description", reason),
109 | }, "\n")))
110 | name = tP.Name()
111 | }
112 |
113 | user.Alert(p, "staff.alert.blacklist", name)
114 | user.Broadcast("command.ban.broadcast", p.Name(), name, reason)
115 | webhook.SendPunishment(p.Name(), name, reason, "Blacklist")
116 | u.Message("command.blacklist.success", name, reason)
117 | }
118 |
--------------------------------------------------------------------------------
/vasar/form/casual_stats.go:
--------------------------------------------------------------------------------
1 | package form
2 |
3 | import (
4 | "fmt"
5 | "github.com/df-mc/dragonfly/server/player"
6 | "github.com/df-mc/dragonfly/server/player/form"
7 | "github.com/sandertv/gophertunnel/minecraft/text"
8 | "github.com/vasar-network/practice/vasar/data"
9 | "github.com/vasar-network/practice/vasar/user"
10 | "github.com/vasar-network/vails/lang"
11 | "time"
12 | )
13 |
14 | // casualStats is a form that displays the casual stats of a player.
15 | type casualStats struct {
16 | // id is the xuid or name of the target player.
17 | id string
18 | }
19 |
20 | // NewCasualStats creates a new casual stats form to send to a player. An error will be returned if the offline user
21 | // or if the offline user has their stats hidden.
22 | func NewCasualStats(p *player.Player, id string) (form.Form, error) {
23 | var (
24 | displayName string
25 | playtimeSession time.Duration
26 | playtimeTotal time.Duration
27 | stats user.Stats
28 | )
29 |
30 | if u, ok := user.LookupXUID(id); ok {
31 | displayName = u.DisplayName()
32 | playtimeSession = time.Since(u.JoinTime()).Round(time.Second)
33 | playtimeTotal = u.PlayTime().Round(time.Second)
34 | stats = u.Stats()
35 | } else {
36 | u, err := data.LoadOfflineUser(id)
37 | if err != nil {
38 | return nil, err
39 | }
40 | if !u.Settings.Privacy.PublicStatistics {
41 | return nil, fmt.Errorf(lang.Translatef(p.Locale(), "command.stats.private"))
42 | }
43 | displayName = u.DisplayName()
44 | playtimeTotal = u.PlayTime().Round(time.Second)
45 | stats = u.Stats
46 | }
47 |
48 | return form.NewMenu(casualStats{id: id}, fmt.Sprintf("%v's Casual Stats", displayName)).WithButtons(
49 | form.NewButton("View Competitive Stats", ""),
50 | ).WithBody(
51 | text.Colourf(" Playtime (Session): %s\n", playtimeSession),
52 | text.Colourf("Playtime (All Time): %s\n", playtimeTotal),
53 | text.Colourf("Kills: %v\n", stats.Kills),
54 | text.Colourf("Killstreak: %v\n", stats.KillStreak),
55 | text.Colourf("Best Killstreak: %v\n", stats.BestKillStreak),
56 | text.Colourf("Deaths: %v\n", stats.Deaths),
57 | ), nil
58 | }
59 |
60 | // Submit ...
61 | func (c casualStats) Submit(s form.Submitter, _ form.Button) {
62 | s.(*player.Player).SendForm(NewCompetitiveStats(c.id))
63 | }
64 |
--------------------------------------------------------------------------------
/vasar/form/competitive_stats.go:
--------------------------------------------------------------------------------
1 | package form
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/player"
5 | "github.com/df-mc/dragonfly/server/player/form"
6 | "github.com/sandertv/gophertunnel/minecraft/text"
7 | "github.com/vasar-network/practice/vasar/data"
8 | "github.com/vasar-network/practice/vasar/game"
9 | "github.com/vasar-network/practice/vasar/user"
10 | "strings"
11 | )
12 |
13 | // competitiveStats is a form that displays the competitive stats of a player.
14 | type competitiveStats struct {
15 | // id is the xuid or name of the target player.
16 | id string
17 | }
18 |
19 | // NewCompetitiveStats creates a new competitive stats form to send to a player.
20 | func NewCompetitiveStats(id string) form.Form {
21 | var displayName string
22 | var stats user.Stats
23 |
24 | if u, ok := user.LookupXUID(id); ok {
25 | displayName = u.DisplayName()
26 | stats = u.Stats()
27 | } else {
28 | u, _ := data.LoadOfflineUser(id)
29 | displayName = u.DisplayName()
30 | stats = u.Stats
31 | }
32 |
33 | var games []string
34 | for _, g := range game.Games() {
35 | games = append(games, text.Colourf("%s: %v", g.Name(), stats.GameElo[strings.ReplaceAll(strings.ToLower(g.Name()), " ", "_")]))
36 | }
37 |
38 | return form.NewMenu(competitiveStats{id: id}, displayName+"'s Competitive Stats").WithButtons(
39 | form.NewButton("View Casual Stats", ""),
40 | ).WithBody(
41 | text.Colourf("Wins: %v", stats.RankedWins),
42 | text.Colourf("\nLosses: %v\n", stats.RankedLosses),
43 | text.Colourf("\nElo\n"),
44 | text.Colourf("Global: %v\n", stats.Elo),
45 | strings.Join(games, "\n "),
46 | )
47 | }
48 |
49 | // Submit ...
50 | func (c competitiveStats) Submit(s form.Submitter, _ form.Button) {
51 | p := s.(*player.Player)
52 | f, err := NewCasualStats(p, c.id)
53 | if err != nil {
54 | p.Message(err.Error())
55 | return
56 | }
57 | p.SendForm(f)
58 | }
59 |
--------------------------------------------------------------------------------
/vasar/form/display_settings.go:
--------------------------------------------------------------------------------
1 | package form
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/player/form"
5 | "github.com/vasar-network/practice/vasar/user"
6 | )
7 |
8 | // display is the form that handles the modification of display settings.
9 | type display struct {
10 | // Scoreboard is a dropdown that allows the user to enable or disable scoreboards.
11 | Scoreboard form.Dropdown
12 | // CPS is a dropdown that allows the user to enable or disable the CPS counter.
13 | CPS form.Dropdown
14 | // u is the user that is using the form.
15 | u *user.User
16 | }
17 |
18 | // NewDisplay creates a new form for the player to modify their display settings.
19 | func NewDisplay(u *user.User) form.Form {
20 | s := u.Settings()
21 | return form.New(display{
22 | Scoreboard: newToggleDropdown("Scoreboard:", s.Display.Scoreboard),
23 | CPS: newToggleDropdown("CPS Counter:", s.Display.CPS),
24 | u: u,
25 | }, "Display Settings")
26 | }
27 |
28 | // Submit ...
29 | func (d display) Submit(form.Submitter) {
30 | s := d.u.Settings()
31 | s.Display.CPS = indexBool(d.CPS)
32 | s.Display.Scoreboard = indexBool(d.Scoreboard)
33 | d.u.SetSettings(s)
34 | if s.Display.Scoreboard {
35 | d.u.Board().SendScoreboard(d.u.Player())
36 | } else if !s.Display.Scoreboard {
37 | d.u.Player().RemoveScoreboard()
38 | }
39 | d.u.Player().SendForm(NewDisplay(d.u))
40 | }
41 |
42 | // Close ...
43 | func (d display) Close(form.Submitter) {
44 | d.u.Player().SendForm(NewSettings(d.u))
45 | }
46 |
--------------------------------------------------------------------------------
/vasar/form/duel_request.go:
--------------------------------------------------------------------------------
1 | package form
2 |
3 | import (
4 | "fmt"
5 | "github.com/df-mc/dragonfly/server/player"
6 | "github.com/df-mc/dragonfly/server/player/form"
7 | "github.com/vasar-network/practice/vasar/game"
8 | "github.com/vasar-network/practice/vasar/game/lobby"
9 | "github.com/vasar-network/practice/vasar/game/match"
10 | "github.com/vasar-network/practice/vasar/user"
11 | "github.com/vasar-network/vails/lang"
12 | "github.com/vasar-network/vap"
13 | "strings"
14 | )
15 |
16 | // duelRequest ...
17 | type duelRequest struct {
18 | to *user.User
19 | }
20 |
21 | // NewDuelRequest ...
22 | func NewDuelRequest(t *user.User) form.Menu {
23 | buttons := make([]form.Button, 0, len(game.Games()))
24 | for _, g := range game.Games() {
25 | buttons = append(buttons, form.NewButton(g.Name(), g.Texture()))
26 | }
27 | return form.NewMenu(duelRequest{to: t}, fmt.Sprintf("Duel %v", t.DisplayName())).WithButtons(buttons...)
28 | }
29 |
30 | // Submit ...
31 | func (d duelRequest) Submit(submitter form.Submitter, pressed form.Button) {
32 | g := game.ByName(strings.Split(pressed.Text, "\n")[0])
33 | submitter.SendForm(newDuelMaps(g, d.to))
34 | }
35 |
36 | // duelMaps ...
37 | type duelMaps struct {
38 | g game.Game
39 | to *user.User
40 |
41 | m map[form.Button]*vap.Vap
42 | }
43 |
44 | // newDuelMaps ...
45 | func newDuelMaps(g game.Game, t *user.User) form.Menu {
46 | a := match.Arenas(g)
47 | m := make(map[form.Button]*vap.Vap, len(a))
48 | buttons := make([]form.Button, 0, len(a))
49 | buttons = append(buttons, form.NewButton("Random", ""))
50 | for _, v := range a {
51 | n, _, _ := v.Arena()
52 | b := form.NewButton(n, "")
53 | m[b] = v
54 | buttons = append(buttons, b)
55 | }
56 | return form.NewMenu(duelMaps{g: g, to: t, m: m}, "Pick a Map").WithButtons(buttons...)
57 | }
58 |
59 | // Submit ...
60 | func (d duelMaps) Submit(submitter form.Submitter, pressed form.Button) {
61 | p := submitter.(*player.Player)
62 | if _, ok := lobby.LookupProvider(p); !ok {
63 | p.Message(lang.Translatef(p.Locale(), "user.feature.disabled"))
64 | return
65 | }
66 |
67 | var v *vap.Vap
68 | if pressed.Text == "Random" {
69 | v = match.RandomArena(d.g)
70 | } else {
71 | v = d.m[pressed]
72 | }
73 |
74 | match.Unranked().RequestDuel(d.g, v, p, d.to.Player())
75 | }
76 |
--------------------------------------------------------------------------------
/vasar/form/duels.go:
--------------------------------------------------------------------------------
1 | package form
2 |
3 | import (
4 | "fmt"
5 | "github.com/df-mc/dragonfly/server/player"
6 | "github.com/df-mc/dragonfly/server/player/form"
7 | "github.com/vasar-network/practice/vasar/game"
8 | "github.com/vasar-network/practice/vasar/game/match"
9 | "github.com/vasar-network/practice/vasar/user"
10 | "strings"
11 | "time"
12 | )
13 |
14 | // duels is a form that allows players to join games of Duels.
15 | type duels struct {
16 | p match.Provider
17 | }
18 |
19 | // NewRankedDuels creates a new duels form with the provided host and user.
20 | func NewRankedDuels() form.Menu {
21 | prov := match.Ranked()
22 | buttons := make([]form.Button, 0, len(game.Games()))
23 | for _, g := range game.Games() {
24 | buttons = append(buttons, form.NewButton(
25 | g.Name()+"\n"+fmt.Sprintf("Queued: %v Playing: %v", prov.QueuedUsers(g), prov.PlayingUsers(g)),
26 | g.Texture()),
27 | )
28 | }
29 | return form.NewMenu(duels{p: prov}, "Join Ranked Queue").WithButtons(buttons...)
30 | }
31 |
32 | // NewUnrankedDuels creates a new duels form with the provided host and user.
33 | func NewUnrankedDuels() form.Menu {
34 | prov := match.Unranked()
35 | buttons := make([]form.Button, 0, len(game.Games()))
36 | for _, g := range game.Games() {
37 | buttons = append(buttons, form.NewButton(
38 | g.Name()+"\n"+fmt.Sprintf("Queued: %v Playing: %v", prov.QueuedUsers(g), prov.PlayingUsers(g)),
39 | g.Texture()),
40 | )
41 | }
42 | return form.NewMenu(duels{p: prov}, "Join Unranked Queue").WithButtons(buttons...)
43 | }
44 |
45 | // Submit ...
46 | func (d duels) Submit(submitter form.Submitter, pressed form.Button) {
47 | p := submitter.(*player.Player)
48 | if u, ok := user.Lookup(p); ok {
49 | if exp := u.QueuedSince().Add(time.Second * 3); exp.After(time.Now()) {
50 | u.Message("queue.message.cooldown", time.Until(exp).Round(time.Millisecond*10))
51 | return
52 | }
53 | }
54 | g := game.ByName(strings.Split(pressed.Text, "\n")[0])
55 | d.p.EnterQueue(g, p)
56 | }
57 |
--------------------------------------------------------------------------------
/vasar/form/ffa.go:
--------------------------------------------------------------------------------
1 | package form
2 |
3 | import (
4 | "fmt"
5 | "github.com/df-mc/dragonfly/server/player"
6 | "github.com/df-mc/dragonfly/server/player/form"
7 | "github.com/sandertv/gophertunnel/minecraft/text"
8 | "github.com/vasar-network/practice/vasar/game"
9 | "github.com/vasar-network/practice/vasar/game/ffa"
10 | "github.com/vasar-network/practice/vasar/game/lobby"
11 | "github.com/vasar-network/vails/lang"
12 | "strings"
13 | )
14 |
15 | // freeForAll is a form that allows players to join games of Free For All.
16 | type freeForAll struct{}
17 |
18 | // NewFFA creates a new FFA form with the provided host and user.
19 | func NewFFA() form.Menu {
20 | var buttons []form.Button
21 | for _, prov := range ffa.Providers() {
22 | status := text.Colourf("Closed")
23 | if prov.Open() {
24 | status = fmt.Sprintf("%v/%v playing", len(prov.Players()), prov.Game().Cap())
25 | }
26 | buttons = append(buttons, form.NewButton(
27 | prov.Game().Name()+"\n"+status,
28 | prov.Game().Texture()),
29 | )
30 | }
31 | return form.NewMenu(freeForAll{}, "Free For All").WithButtons(buttons...)
32 | }
33 |
34 | // Submit ...
35 | func (f freeForAll) Submit(submitter form.Submitter, pressed form.Button) {
36 | p := submitter.(*player.Player)
37 | if _, ok := lobby.LookupProvider(p); !ok {
38 | p.Message(lang.Translatef(p.Locale(), "user.feature.disabled"))
39 | return
40 | }
41 | g := game.ByName(strings.Split(pressed.Text, "\n")[0])
42 | for _, prov := range ffa.Providers() {
43 | if prov.Game() == g {
44 | if !prov.Open() {
45 | p.Message(lang.Translatef(p.Locale(), "arena.closed"))
46 | return
47 | }
48 | if len(prov.Players()) >= prov.Game().Cap() {
49 | p.Message(lang.Translatef(p.Locale(), "arena.full"))
50 | return
51 | }
52 | lobby.Lobby().RemovePlayer(p, false)
53 | prov.AddPlayer(p)
54 | break
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/vasar/form/gameplay_settings.go:
--------------------------------------------------------------------------------
1 | package form
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/player/form"
5 | "github.com/vasar-network/practice/vasar/user"
6 | )
7 |
8 | // gameplay is the form that handles the modification of gameplay settings.
9 | type gameplay struct {
10 | // ToggleSprint is a dropdown that allows the user to toggle automatic sprinting.
11 | ToggleSprint form.Dropdown
12 | // AutoReapplyKit is a dropdown that allows the user to automatically reapply their kit after killing an entity.
13 | AutoReapplyKit form.Dropdown
14 | // PreventInterference is a dropdown that allows the user to enable or disable anti-interference.
15 | PreventInterference form.Dropdown
16 | // PreventClutter is a dropdown that allows the user to enable or disable anti-clutter.
17 | PreventClutter form.Dropdown
18 | // InstantRespawn is a dropdown that allows the user to enable or disable instant respawn.
19 | InstantRespawn form.Dropdown
20 | // u is the user that is using the form.
21 | u *user.User
22 | }
23 |
24 | // NewGameplay creates a new form for the player to modify their gameplay settings.
25 | func NewGameplay(u *user.User) form.Form {
26 | s := u.Settings()
27 | return form.New(gameplay{
28 | ToggleSprint: newToggleDropdown("Toggle Sprint:", s.Gameplay.ToggleSprint),
29 | AutoReapplyKit: newToggleDropdown("Auto-Rekit:", s.Gameplay.AutoReapplyKit),
30 | PreventInterference: newToggleDropdown("Anti-Interference:", s.Gameplay.PreventInterference),
31 | PreventClutter: newToggleDropdown("Anti-Clutter (Anti-Interference Required):", s.Gameplay.PreventClutter),
32 | InstantRespawn: newToggleDropdown("Instant Respawn:", s.Gameplay.InstantRespawn),
33 | u: u,
34 | }, "Gameplay Settings")
35 | }
36 |
37 | // Submit ...
38 | func (d gameplay) Submit(form.Submitter) {
39 | s := d.u.Settings()
40 | s.Gameplay.ToggleSprint = indexBool(d.ToggleSprint)
41 | s.Gameplay.AutoReapplyKit = indexBool(d.AutoReapplyKit)
42 | s.Gameplay.PreventInterference = indexBool(d.PreventInterference)
43 | s.Gameplay.PreventClutter = indexBool(d.PreventClutter)
44 | s.Gameplay.InstantRespawn = indexBool(d.InstantRespawn)
45 | d.u.SetSettings(s)
46 | d.u.Player().SendForm(NewGameplay(d.u))
47 | }
48 |
49 | // Close ...
50 | func (d gameplay) Close(form.Submitter) {
51 | d.u.Player().SendForm(NewSettings(d.u))
52 | }
53 |
--------------------------------------------------------------------------------
/vasar/form/matchmaking_settings.go:
--------------------------------------------------------------------------------
1 | package form
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/player/form"
5 | "github.com/sandertv/gophertunnel/minecraft/text"
6 | "github.com/vasar-network/practice/vasar/user"
7 | "github.com/vasar-network/vails/role"
8 | )
9 |
10 | // matchmaking is the form that handles the modification of matchmaking settings.
11 | type matchmaking struct {
12 | // MatchWithMobile is a toggle allowing the user to enable or disable matching with mobile players.
13 | MatchWithMobile form.Toggle
14 | // MatchWithController is a toggle allowing the user to enable or disable matching with controller players.
15 | MatchWithController form.Toggle
16 | // MatchWithKeyboard is a toggle allowing the user to enable or disable matching with keyboard players.
17 | MatchWithKeyboard form.Toggle
18 | // PingRange is a slider allowing the user to set the ping range opponents must fall within.
19 | PingRange form.StepSlider
20 | // u is the user that is using the form.
21 | u *user.User
22 | }
23 |
24 | // NewMatchmaking creates a new form for the player to modify their matchmaking settings.
25 | func NewMatchmaking(u *user.User) form.Form {
26 | s := u.Settings()
27 | return form.New(matchmaking{
28 | MatchWithMobile: form.NewToggle("Queue Against Touch Players", s.Matchmaking.MatchWithMobile),
29 | MatchWithController: form.NewToggle("Queue Against Controller Players", s.Matchmaking.MatchWithController),
30 | MatchWithKeyboard: form.NewToggle("Queue Against Mouse/Keyboard Players", s.Matchmaking.MatchWithKeyboard),
31 | PingRange: form.NewStepSlider("Ping Range", []string{
32 | text.Colourf("Unrestricted"),
33 | "25",
34 | "50",
35 | "75",
36 | "100",
37 | "125",
38 | "150",
39 | }, int(s.Matchmaking.PingRange)),
40 | u: u,
41 | }, "Matchmaking Settings")
42 | }
43 |
44 | // Submit ...
45 | func (d matchmaking) Submit(form.Submitter) {
46 | s := d.u.Settings()
47 | p := d.u.Player()
48 | if !d.u.Roles().Contains(role.Plus{}) && !d.u.Roles().Contains(role.Operator{}) {
49 | d.u.Message("setting.plus")
50 | return
51 | }
52 | if !d.MatchWithMobile.Value() && d.u.DeviceGroup().Compare(user.DeviceGroupMobile()) {
53 | d.u.Message("form.matchmaking.forced.queueing")
54 | p.SendForm(NewMatchmaking(d.u))
55 | return
56 | }
57 | s.Matchmaking.MatchWithMobile = d.MatchWithMobile.Value()
58 | if !d.MatchWithController.Value() && d.u.DeviceGroup().Compare(user.DeviceGroupController()) {
59 | d.u.Message("form.matchmaking.forced.queueing")
60 | p.SendForm(NewMatchmaking(d.u))
61 | return
62 | }
63 | s.Matchmaking.MatchWithController = d.MatchWithController.Value()
64 | if !d.MatchWithKeyboard.Value() && d.u.DeviceGroup().Compare(user.DeviceGroupKeyboardMouse()) {
65 | d.u.Message("form.matchmaking.forced.queueing")
66 | p.SendForm(NewMatchmaking(d.u))
67 | return
68 | }
69 | s.Matchmaking.MatchWithKeyboard = d.MatchWithKeyboard.Value()
70 | s.Matchmaking.PingRange = uint8(d.PingRange.Value())
71 | d.u.SetSettings(s)
72 | d.u.Player().SendForm(NewMatchmaking(d.u))
73 | }
74 |
75 | // Close ...
76 | func (d matchmaking) Close(form.Submitter) {
77 | d.u.Player().SendForm(NewSettings(d.u))
78 | }
79 |
--------------------------------------------------------------------------------
/vasar/form/mute.go:
--------------------------------------------------------------------------------
1 | package form
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/player"
5 | "github.com/df-mc/dragonfly/server/player/form"
6 | "github.com/vasar-network/practice/vasar/data"
7 | "github.com/vasar-network/practice/vasar/user"
8 | "github.com/vasar-network/vails/role"
9 | "github.com/vasar-network/vails/webhook"
10 | "golang.org/x/exp/maps"
11 | "math/rand"
12 | "sort"
13 | "strings"
14 | "time"
15 | )
16 |
17 | // mute is a form that allows a user to issue a mute.
18 | type mute struct {
19 | // Reason is a dropdown that allows a user to select a mute reason.
20 | Reason form.Dropdown
21 | // OnlinePlayer is a dropdown that allows a user to select an online player.
22 | OnlinePlayer form.Dropdown
23 | // OfflinePlayer is an input field that allows a user to enter an offline player.
24 | OfflinePlayer form.Input
25 | // online is a list of online players' XUIDs indexed by their names.
26 | online map[string]string
27 | // p is the player that is using the form.
28 | p *player.Player
29 | }
30 |
31 | // NewMute creates a new form to issue a mute.
32 | func NewMute(p *player.Player) form.Form {
33 | online := make(map[string]string)
34 | for _, u := range user.All() {
35 | online[u.Player().Name()] = u.Player().XUID()
36 | }
37 | names := [...]string{"Steve Harvey", "Elon Musk", "Bill Gates", "Mark Zuckerberg", "Jeff Bezos", "Warren Buffet", "Larry Page", "Sergey Brin", "Larry Ellison", "Tim Cook", "Steve Ballmer", "Daniel Larson", "Steve"}
38 | list := maps.Keys(online)
39 | sort.Strings(list)
40 | return form.New(mute{
41 | Reason: form.NewDropdown("Reason", []string{"Spam", "Toxicity", "Advertisement"}, 0),
42 | OnlinePlayer: form.NewDropdown("Online Player", list, 0),
43 | OfflinePlayer: form.NewInput("Offline Player", "", names[rand.Intn(len(names)-1)]),
44 | online: online,
45 | p: p,
46 | }, "Mute")
47 | }
48 |
49 | // Submit ...
50 | func (m mute) Submit(form.Submitter) {
51 | u, ok := user.Lookup(m.p)
52 | if !ok {
53 | // User somehow left midway through the form.
54 | return
55 | }
56 | if !u.Roles().Contains(role.Trial{}, role.Operator{}) {
57 | // In case the user's role was removed while the form was open.
58 | return
59 | }
60 | var length time.Duration
61 | reason := m.Reason.Options[m.Reason.Value()]
62 | switch reason {
63 | case "Spam":
64 | length = time.Hour * 6
65 | case "Toxicity":
66 | length = time.Hour * 9
67 | case "Advertising":
68 | length = time.Hour * 24 * 3
69 | default:
70 | panic("should never happen")
71 | }
72 |
73 | mu := user.Punishment{
74 | Staff: m.p.Name(),
75 | Reason: reason,
76 | Occurrence: time.Now(),
77 | Expiration: time.Now().Add(length),
78 | }
79 | if offlineName := strings.TrimSpace(m.OfflinePlayer.Value()); offlineName != "" {
80 | if strings.EqualFold(offlineName, m.p.Name()) {
81 | u.Message("command.mute.self")
82 | return
83 | }
84 | t, err := data.LoadOfflineUser(offlineName)
85 | if err != nil {
86 | u.Message("command.target.unknown")
87 | return
88 | }
89 | if t.Roles.Contains(role.Operator{}) {
90 | u.Message("command.mute.operator")
91 | return
92 | }
93 | if !t.Mute.Expired() {
94 | u.Message("command.mute.already")
95 | return
96 | }
97 | t.Mute = mu
98 | _ = data.SaveOfflineUser(t)
99 |
100 | user.Alert(m.p, "staff.alert.mute", t.DisplayName(), reason)
101 | webhook.SendPunishment(m.p.Name(), t.DisplayName(), reason, "Mute")
102 | u.Message("command.mute.success", t.DisplayName(), reason)
103 | return
104 | }
105 | t, ok := user.LookupXUID(m.online[m.OnlinePlayer.Options[m.OnlinePlayer.Value()]])
106 | if !ok {
107 | u.Message("command.target.unknown")
108 | return
109 | }
110 | if t.Roles().Contains(role.Operator{}) {
111 | u.Message("command.mute.operator")
112 | return
113 | }
114 | if _, ok := t.Mute(); ok {
115 | u.Message("command.mute.already")
116 | return
117 | }
118 | t.SetMute(mu)
119 | _ = data.SaveUser(t) // Save in case of a server crash or anything that may cause the data to not get saved.
120 |
121 | user.Alert(m.p, "staff.alert.mute", t.Player().Name(), reason)
122 | webhook.SendPunishment(m.p.Name(), t.Player().Name(), reason, "Mute")
123 | u.Message("command.mute.success", t.Player().Name(), reason)
124 | }
125 |
--------------------------------------------------------------------------------
/vasar/form/post_match_stats.go:
--------------------------------------------------------------------------------
1 | package form
2 |
3 | import (
4 | "fmt"
5 | "github.com/df-mc/dragonfly/server/item"
6 | "github.com/df-mc/dragonfly/server/player/form"
7 | "github.com/sandertv/gophertunnel/minecraft/text"
8 | "github.com/vasar-network/practice/vasar/game/match"
9 | it "github.com/vasar-network/practice/vasar/item"
10 | "github.com/vasar-network/practice/vasar/user"
11 | "golang.org/x/exp/maps"
12 | "golang.org/x/exp/slices"
13 | "sort"
14 | "strings"
15 | )
16 |
17 | // postMatchStats is a form that displays the post-match stats of a match
18 | type postMatchStats struct {
19 | // stats maps between a player and their stats.
20 | stats map[string]any
21 | // index maps between a form.Button and a player.
22 | index map[form.Button]string
23 | // u is the user that is using the form.
24 | u *user.User
25 | }
26 |
27 | // specificPostMatchStats is a form that displays the stats of a specific player.
28 | type specificPostMatchStats struct {
29 | // name is the name of the player.
30 | name string
31 | // stats is the stats to be displayed.
32 | stats match.Stats
33 | // u is the user that is using the form.
34 | u *user.User
35 | }
36 |
37 | // NewPostMatchStats creates a new form for the player to view the post-match stats of a match.
38 | func NewPostMatchStats(u *user.User) form.Form {
39 | p := u.Player()
40 | stats, _ := u.PostMatchStats()
41 |
42 | selections := maps.Keys(stats)
43 |
44 | ind := slices.Index(selections, p.Name())
45 | selections = slices.Delete(selections, ind, ind+1)
46 |
47 | sort.Strings(selections)
48 | selections = append([]string{p.Name()}, selections...)
49 |
50 | s := postMatchStats{
51 | index: make(map[form.Button]string),
52 | stats: stats,
53 | u: u,
54 | }
55 | m := form.NewMenu(s, "Post Match Details")
56 | for _, v := range selections {
57 | button := form.NewButton(fmt.Sprintf("%v's Statistics", v), "")
58 | if v == p.Name() {
59 | button = form.NewButton("My Statistics", "")
60 | }
61 |
62 | s.index[button] = v
63 | m = m.WithButtons(button)
64 | }
65 | return m
66 | }
67 |
68 | // NewSpecificPostMatchStats creates a new form for the player to view the stats of a specific player.
69 | func NewSpecificPostMatchStats(u *user.User, name string, stats match.Stats) form.Form {
70 | s := specificPostMatchStats{
71 | name: name,
72 | stats: stats,
73 | u: u,
74 | }
75 |
76 | b := &strings.Builder{}
77 | b.WriteString(fmt.Sprintf("Total Hits: %d\n", stats.Hits))
78 | b.WriteString(fmt.Sprintf("Damage Dealt: %.2f\n", stats.Damage))
79 |
80 | var potions, apples int
81 | for _, i := range stats.Items {
82 | switch i.Item().(type) {
83 | case it.VasarPotion:
84 | potions += i.Count()
85 | case item.GoldenApple:
86 | apples += i.Count()
87 | }
88 | }
89 | if potions > 0 {
90 | b.WriteString(fmt.Sprintf("Splash Potions: %d\n", potions))
91 | }
92 | if apples > 0 {
93 | b.WriteString(fmt.Sprintf("Golden Apples: %d\n", apples))
94 | }
95 |
96 | m := form.NewMenu(s, fmt.Sprintf("%v's Statistics", name)).WithBody(text.Colourf(b.String()))
97 | return m
98 | }
99 |
100 | // Submit ...
101 | func (p postMatchStats) Submit(_ form.Submitter, pressed form.Button) {
102 | name := p.index[pressed]
103 | stats := p.stats[name].(match.Stats)
104 | p.u.Player().SendForm(NewSpecificPostMatchStats(p.u, name, stats))
105 | }
106 |
107 | // Submit ...
108 | func (s specificPostMatchStats) Submit(form.Submitter, form.Button) {
109 | s.u.Player().SendForm(NewSpecificPostMatchStats(s.u, s.name, s.stats))
110 | }
111 |
--------------------------------------------------------------------------------
/vasar/form/privacy_settings.go:
--------------------------------------------------------------------------------
1 | package form
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/player/form"
5 | "github.com/vasar-network/practice/vasar/user"
6 | )
7 |
8 | // privacy is the form that handles the modification of privacy settings.
9 | type privacy struct {
10 | // PrivateMessages is a dropdown that allows the user to enable or disable private messages from others.
11 | PrivateMessages form.Dropdown
12 | // PublicStatistics is a dropdown that allows the user to enable or disable public statistics.
13 | PublicStatistics form.Dropdown
14 | // DuelRequests is a dropdown that allows the user to enable or disable duel requests from others.
15 | DuelRequests form.Dropdown
16 | // u is the user that is using the form.
17 | u *user.User
18 | }
19 |
20 | // NewPrivacy creates a new form for the player to modify their privacy settings.
21 | func NewPrivacy(u *user.User) form.Form {
22 | s := u.Settings()
23 | return form.New(privacy{
24 | PrivateMessages: newToggleDropdown("Allow others to private message me:", s.Privacy.PrivateMessages),
25 | PublicStatistics: newToggleDropdown("Allow others to view my stats:", s.Privacy.PublicStatistics),
26 | DuelRequests: newToggleDropdown("Allow others to send me duel requests:", s.Privacy.DuelRequests),
27 | u: u,
28 | }, "Privacy Settings")
29 | }
30 |
31 | // Submit ...
32 | func (d privacy) Submit(form.Submitter) {
33 | s := d.u.Settings()
34 | s.Privacy.PrivateMessages = indexBool(d.PrivateMessages)
35 | s.Privacy.PublicStatistics = indexBool(d.PublicStatistics)
36 | s.Privacy.DuelRequests = indexBool(d.DuelRequests)
37 | d.u.SetSettings(s)
38 | d.u.Player().SendForm(NewPrivacy(d.u))
39 | }
40 |
41 | // Close ...
42 | func (d privacy) Close(form.Submitter) {
43 | d.u.Player().SendForm(NewSettings(d.u))
44 | }
45 |
--------------------------------------------------------------------------------
/vasar/form/settings.go:
--------------------------------------------------------------------------------
1 | package form
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/player/form"
5 | "github.com/sandertv/gophertunnel/minecraft/text"
6 | "github.com/vasar-network/practice/vasar/user"
7 | )
8 |
9 | // settings is a form menu which contains all possible player setting categories, allowing the user to configure them
10 | // at will.
11 | type settings struct {
12 | u *user.User
13 | }
14 |
15 | // NewSettings creates a new settings form to send to a player.
16 | func NewSettings(u *user.User) form.Menu {
17 | return form.NewMenu(settings{u: u}, text.Colourf("Settings")).WithButtons(
18 | form.NewButton("Display", ""),
19 | form.NewButton("Visual", ""),
20 | form.NewButton("Gameplay", ""),
21 | form.NewButton("Privacy", ""),
22 | form.NewButton("Matchmaking", ""),
23 | form.NewButton("Advanced", ""),
24 | )
25 | }
26 |
27 | // Submit ...
28 | func (s settings) Submit(_ form.Submitter, pressed form.Button) {
29 | p := s.u.Player()
30 | switch pressed.Text {
31 | case "Display":
32 | p.SendForm(NewDisplay(s.u))
33 | case "Visual":
34 | p.SendForm(NewVisual(s.u))
35 | case "Gameplay":
36 | p.SendForm(NewGameplay(s.u))
37 | case "Privacy":
38 | p.SendForm(NewPrivacy(s.u))
39 | case "Matchmaking":
40 | p.SendForm(NewMatchmaking(s.u))
41 | case "Advanced":
42 | p.SendForm(NewAdvanced(s.u))
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/vasar/form/spectate.go:
--------------------------------------------------------------------------------
1 | package form
2 |
3 | import (
4 | "fmt"
5 | "github.com/df-mc/dragonfly/server/player"
6 | "github.com/df-mc/dragonfly/server/player/form"
7 | "github.com/vasar-network/practice/vasar/game/match"
8 | "golang.org/x/exp/maps"
9 | )
10 |
11 | // spectateDuels ...
12 | type spectateDuels struct {
13 | m map[form.Button]*match.Match
14 | }
15 |
16 | // NewSpectate ...
17 | func NewSpectate() form.Menu {
18 | var matches = make(map[form.Button]*match.Match)
19 | for _, m := range match.Unranked().RunningMatches() {
20 | players := m.Players()
21 | b := form.NewButton(fmt.Sprintf("%v vs %v\n Unranked %v | Spectating: %v", players[0].DisplayName(), players[1].DisplayName(), m.Game().Name(), len(m.Spectators())), "")
22 | matches[b] = m
23 | }
24 | for _, m := range match.Ranked().RunningMatches() {
25 | players := m.Players()
26 | b := form.NewButton(fmt.Sprintf("%v vs %v\n Ranked %v | Spectating: %v", players[0].DisplayName(), players[1].DisplayName(), m.Game().Name(), len(m.Spectators())), "")
27 | matches[b] = m
28 | }
29 | return form.NewMenu(spectateDuels{m: matches}, "Spectate").WithButtons(maps.Keys(matches)...)
30 | }
31 |
32 | // Submit ...
33 | func (s spectateDuels) Submit(submitter form.Submitter, pressed form.Button) {
34 | s.m[pressed].AddSpectator(submitter.(*player.Player), false)
35 | }
36 |
--------------------------------------------------------------------------------
/vasar/form/toggle_dropdown.go:
--------------------------------------------------------------------------------
1 | package form
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/player/form"
5 | "github.com/sandertv/gophertunnel/minecraft/text"
6 | )
7 |
8 | // newToggleDropdown returns a new form dropdown with the options Enabled and Disabled.
9 | func newToggleDropdown(name string, enabled bool) form.Dropdown {
10 | return form.NewDropdown(name, []string{
11 | text.Colourf("Enabled"),
12 | text.Colourf("Disabled"),
13 | }, boolIndex(enabled))
14 | }
15 |
16 | // boolIndex converts a boolean to an index.
17 | func boolIndex(b bool) int {
18 | if b {
19 | return 0
20 | }
21 | return 1
22 | }
23 |
24 | // indexBool converts an index to a bool.
25 | func indexBool(dropdown form.Dropdown) bool {
26 | return dropdown.Value() == 0
27 | }
28 |
--------------------------------------------------------------------------------
/vasar/form/visual_settings.go:
--------------------------------------------------------------------------------
1 | package form
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/player/form"
5 | "github.com/vasar-network/practice/vasar/user"
6 | )
7 |
8 | // visual is the form that handles the modification of visual settings.
9 | type visual struct {
10 | // Lightning is a dropdown that allows the user to enable or disable lightning.
11 | Lightning form.Dropdown
12 | // Splashes is a dropdown that allows the user to enable or disable potion splashes.
13 | Splashes form.Dropdown
14 | // PearlAnimation is a dropdown that allows the user to enable or disable the pearl animation.
15 | PearlAnimation form.Dropdown
16 | // u is the user that is using the form.
17 | u *user.User
18 | }
19 |
20 | // NewVisual creates a new form for the player to modify their visual settings.
21 | func NewVisual(u *user.User) form.Form {
22 | s := u.Settings()
23 | return form.New(visual{
24 | Lightning: newToggleDropdown("Lightning:", s.Visual.Lightning),
25 | Splashes: newToggleDropdown("Potion Splashes:", s.Visual.Splashes),
26 | PearlAnimation: newToggleDropdown("Pearl Animation:", s.Visual.PearlAnimation),
27 | u: u,
28 | }, "Visual Settings")
29 | }
30 |
31 | // Submit ...
32 | func (d visual) Submit(form.Submitter) {
33 | s := d.u.Settings()
34 | s.Visual.Lightning = indexBool(d.Lightning)
35 | s.Visual.Splashes = indexBool(d.Splashes)
36 | s.Visual.PearlAnimation = indexBool(d.PearlAnimation)
37 | d.u.SetSettings(s)
38 | d.u.Player().SendForm(NewVisual(d.u))
39 | }
40 |
41 | // Close ...
42 | func (d visual) Close(form.Submitter) {
43 | d.u.Player().SendForm(NewSettings(d.u))
44 | }
45 |
--------------------------------------------------------------------------------
/vasar/game/ffa/provider.go:
--------------------------------------------------------------------------------
1 | package ffa
2 |
3 | import (
4 | "github.com/df-mc/atomic"
5 | "github.com/vasar-network/practice/vasar/board"
6 | "github.com/vasar-network/practice/vasar/game/lobby"
7 | "sync"
8 | "time"
9 |
10 | "github.com/df-mc/dragonfly/server/player"
11 | "github.com/df-mc/dragonfly/server/world"
12 | "github.com/go-gl/mathgl/mgl64"
13 | "github.com/vasar-network/practice/vasar/game"
14 | "github.com/vasar-network/practice/vasar/game/kit"
15 | "github.com/vasar-network/practice/vasar/user"
16 | "github.com/vasar-network/vails/sets"
17 | )
18 |
19 | // providers maps between a *player.Player and a *Provider.
20 | var providers sync.Map
21 |
22 | // LookupProvider looks up the *Provider of the *player.Player passed.
23 | func LookupProvider(p *player.Player) (*Provider, bool) {
24 | h, ok := providers.Load(p)
25 | if ok {
26 | return h.(*Provider), ok
27 | }
28 | return nil, false
29 | }
30 |
31 | // ffas ...
32 | var ffas []*Provider
33 |
34 | // Providers returns all the ffa providers.
35 | func Providers() []*Provider {
36 | return ffas
37 | }
38 |
39 | // Provider is a simple FFA provider for any FFA game.
40 | type Provider struct {
41 | w *world.World
42 | game game.Game
43 |
44 | playerMu sync.Mutex
45 | players sets.Set[*player.Player]
46 |
47 | open atomic.Bool
48 | pvp atomic.Bool
49 | }
50 |
51 | // NewProvider ...
52 | func NewProvider(game game.Game, w *world.World) *Provider {
53 | p := &Provider{
54 | players: make(sets.Set[*player.Player]),
55 | open: *atomic.NewBool(true),
56 | pvp: *atomic.NewBool(true),
57 | game: game,
58 | w: w,
59 | }
60 | ffas = append(ffas, p)
61 | return p
62 | }
63 |
64 | // Game ...
65 | func (s *Provider) Game() game.Game {
66 | return s.game
67 | }
68 |
69 | // Players ...
70 | func (s *Provider) Players() []*player.Player {
71 | s.playerMu.Lock()
72 | defer s.playerMu.Unlock()
73 | return s.players.Values()
74 | }
75 |
76 | // PlayerCount ...
77 | func (s *Provider) PlayerCount() int {
78 | s.playerMu.Lock()
79 | defer s.playerMu.Unlock()
80 | return len(s.players)
81 | }
82 |
83 | // AddPlayer ...
84 | func (s *Provider) AddPlayer(p *player.Player) {
85 | providers.Store(p, s)
86 |
87 | s.playerMu.Lock()
88 | s.players.Add(p)
89 | s.playerMu.Unlock()
90 |
91 | lobby.Lobby().RemovePlayer(p, false)
92 |
93 | s.w.AddEntity(p)
94 | p.SetAttackImmunity(time.Second * 3)
95 | p.Teleport(s.w.Spawn().Vec3Middle())
96 |
97 | var fall float64
98 | switch s.Game() {
99 | case game.NoDebuff():
100 | fall = 1.5
101 | case game.Sumo():
102 | fall = 0.5
103 | }
104 | time.AfterFunc(time.Millisecond*300, func() {
105 | p.SetVelocity(mgl64.Vec3{0, -fall, 0})
106 | })
107 |
108 | kit.Apply(s.game.Kit(true), p)
109 | if u, ok := user.Lookup(p); ok {
110 | u.SetBoard(s)
111 | }
112 | }
113 |
114 | // RemovePlayer ...
115 | func (s *Provider) RemovePlayer(p *player.Player, force bool) {
116 | providers.Delete(p)
117 |
118 | s.playerMu.Lock()
119 | s.players.Delete(p)
120 | s.playerMu.Unlock()
121 |
122 | if !force {
123 | lobby.Lobby().AddPlayer(p)
124 | }
125 | }
126 |
127 | // SendScoreboard ...
128 | func (s *Provider) SendScoreboard(p *player.Player) {
129 | if u, ok := user.Lookup(p); ok {
130 | stats := u.Stats()
131 | board.Send(p, "scoreboard.ffa", stats.Kills, stats.KillStreak, stats.Deaths)
132 | }
133 | }
134 |
135 | // Open checks if the arena is open.
136 | func (s *Provider) Open() bool {
137 | return s.open.Load()
138 | }
139 |
140 | // PvP checks if PvP is enabled for the provider.
141 | func (s *Provider) PvP() bool {
142 | return s.pvp.Load()
143 | }
144 |
145 | // ToggleStatus will toggle the open/closed status of the arena.
146 | func (s *Provider) ToggleStatus() (old bool) {
147 | return s.open.Toggle()
148 | }
149 |
150 | // TogglePvP toggles PvP for the provider and returns the old value.
151 | func (s *Provider) TogglePvP() bool {
152 | return s.pvp.Toggle()
153 | }
154 |
--------------------------------------------------------------------------------
/vasar/game/game.go:
--------------------------------------------------------------------------------
1 | package game
2 |
3 | import (
4 | "github.com/vasar-network/practice/vasar/game/kit"
5 | "github.com/vasar-network/vails"
6 | "strings"
7 | )
8 |
9 | // Game represents a Variant of a game.
10 | type Game struct {
11 | id uint8
12 | name string
13 | texture string
14 | }
15 |
16 | // NoDebuff returns the NoDebuff game.
17 | func NoDebuff() Game {
18 | return Game{0, "NoDebuff", "textures/items/potion_bottle_splash_heal"}
19 | }
20 |
21 | // Debuff returns the Debuff game.
22 | func Debuff() Game {
23 | return Game{1, "Debuff", "textures/items/potion_bottle_splash_poison"}
24 | }
25 |
26 | // Gapple returns the Gapple game.
27 | func Gapple() Game {
28 | return Game{2, "Gapple", "textures/items/apple_golden"}
29 | }
30 |
31 | // Soup returns the Soup game.
32 | func Soup() Game {
33 | return Game{3, "Soup", "textures/items/mushroom_stew"}
34 | }
35 |
36 | // Boxing returns the Boxing game.
37 | func Boxing() Game {
38 | return Game{4, "Boxing", "textures/items/slimeball"}
39 | }
40 |
41 | // StickFight returns the StickFight game.
42 | func StickFight() Game {
43 | return Game{5, "Stick Fight", "textures/items/stick"}
44 | }
45 |
46 | // Sumo returns the Sumo game.
47 | func Sumo() Game {
48 | return Game{id: 6, name: "Sumo", texture: "textures/items/feather"}
49 | }
50 |
51 | // Combo returns the Combo game.
52 | func Combo() Game {
53 | return Game{7, "Combo", "textures/items/fish_pufferfish_raw"}
54 | }
55 |
56 | // BuildUHC returns the BuildUHC game.
57 | func BuildUHC() Game {
58 | return Game{8, "BuildUHC", "textures/items/bucket_lava"}
59 | }
60 |
61 | // Games returns all the games supported.
62 | func Games() []Game {
63 | return []Game{NoDebuff(), Boxing(), BuildUHC(), Sumo(), StickFight(), Soup(), Combo(), Gapple(), Debuff()}
64 | }
65 |
66 | // FFA returns all FFA-supported games.
67 | func FFA() []Game {
68 | return []Game{NoDebuff(), Sumo()}
69 | }
70 |
71 | // ByName returns the game with the given name.
72 | func ByName(name string) Game {
73 | for _, g := range Games() {
74 | if g.name == name {
75 | return g
76 | }
77 | }
78 | panic("should never happen")
79 | }
80 |
81 | // ByString returns the game with the given string.
82 | func ByString(s string) Game {
83 | for _, g := range Games() {
84 | if g.String() == s {
85 | return g
86 | }
87 | }
88 | panic("should never happen")
89 | }
90 |
91 | // ByID returns the game with the given ID.
92 | func ByID(id byte) Game {
93 | for _, g := range Games() {
94 | if g.id == id {
95 | return g
96 | }
97 | }
98 | panic("should never happen")
99 | }
100 |
101 | // Name ...
102 | func (g Game) Name() string {
103 | return g.name
104 | }
105 |
106 | // String ...
107 | func (g Game) String() string {
108 | return strings.ReplaceAll(strings.ToLower(g.name), " ", "_")
109 | }
110 |
111 | // Texture ...
112 | func (g Game) Texture() string {
113 | return g.texture
114 | }
115 |
116 | // Kit ...
117 | func (g Game) Kit(ffa bool) vails.Kit {
118 | switch g {
119 | case NoDebuff():
120 | return kit.NoDebuff{FFA: ffa}
121 | case Debuff():
122 | return kit.Debuff{}
123 | case Gapple():
124 | return kit.Gapple{}
125 | case Soup():
126 | return kit.Soup{FFA: ffa}
127 | case Boxing():
128 | return kit.Boxing{}
129 | case StickFight():
130 | return kit.StickFight{}
131 | case Sumo():
132 | return kit.Sumo{FFA: ffa}
133 | case Combo():
134 | return kit.Combo{}
135 | case BuildUHC():
136 | return kit.BuildUHC{}
137 | }
138 | panic("should never happen")
139 | }
140 |
141 | // Cap ...
142 | func (g Game) Cap() int {
143 | switch g {
144 | case NoDebuff():
145 | return 60
146 | case Sumo():
147 | return 30
148 | }
149 | panic("should never happen")
150 | }
151 |
--------------------------------------------------------------------------------
/vasar/game/healing/source.go:
--------------------------------------------------------------------------------
1 | package healing
2 |
3 | type (
4 | // SourceKit is a custom healing source applied when a player is healed by a kit.
5 | SourceKit struct{}
6 | // SourceStew is a custom healing source applied when a player is healed by using stew.
7 | SourceStew struct{}
8 | // SourceKill is a custom healing soruce applied when a player is healed by killing another player.
9 | SourceKill struct{}
10 | )
11 |
12 | // HealingSource ...
13 | func (SourceKit) HealingSource() {}
14 |
15 | // HealingSource ...
16 | func (SourceStew) HealingSource() {}
17 |
18 | // HealingSource ...
19 | func (SourceKill) HealingSource() {}
20 |
--------------------------------------------------------------------------------
/vasar/game/kit/boxing.go:
--------------------------------------------------------------------------------
1 | package kit
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/entity/effect"
5 | "github.com/df-mc/dragonfly/server/item"
6 | "github.com/df-mc/dragonfly/server/item/enchantment"
7 | "github.com/df-mc/dragonfly/server/player"
8 | "time"
9 | )
10 |
11 | // Boxing represents the kit given when players join Boxing.
12 | type Boxing struct{}
13 |
14 | // Items ...
15 | func (Boxing) Items(*player.Player) [36]item.Stack {
16 | return [36]item.Stack{
17 | item.NewStack(item.Sword{Tier: item.ToolTierDiamond}, 1).WithEnchantments(item.NewEnchantment(enchantment.Unbreaking{}, 20)),
18 | }
19 | }
20 |
21 | // Armour ...
22 | func (Boxing) Armour(*player.Player) [4]item.Stack {
23 | return [4]item.Stack{}
24 | }
25 |
26 | // Effects ...
27 | func (Boxing) Effects(*player.Player) []effect.Effect {
28 | return []effect.Effect{
29 | effect.New(effect.Speed{}, 1, time.Hour*24).WithoutParticles(),
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/vasar/game/kit/builduhc.go:
--------------------------------------------------------------------------------
1 | package kit
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/block"
5 | "github.com/df-mc/dragonfly/server/entity/effect"
6 | "github.com/df-mc/dragonfly/server/item"
7 | "github.com/df-mc/dragonfly/server/item/enchantment"
8 | "github.com/df-mc/dragonfly/server/player"
9 | "github.com/sandertv/gophertunnel/minecraft/text"
10 | it "github.com/vasar-network/practice/vasar/item"
11 | "time"
12 | )
13 |
14 | // BuildUHC represents the kit given when players join BuildUHC.
15 | type BuildUHC struct{}
16 |
17 | // Items ...
18 | func (BuildUHC) Items(*player.Player) [36]item.Stack {
19 | durability := item.NewEnchantment(enchantment.Unbreaking{}, 10)
20 | efficiency := item.NewEnchantment(enchantment.Efficiency{}, 1)
21 | return [36]item.Stack{
22 | item.NewStack(item.Sword{Tier: item.ToolTierDiamond}, 1).WithEnchantments(durability),
23 | item.NewStack(it.FishingRod{}, 1),
24 | item.NewStack(item.Bow{}, 1),
25 | item.NewStack(item.GoldenApple{}, 6),
26 | item.NewStack(item.GoldenApple{}, 3).WithCustomName("§r"+text.Colourf("Golden Head")).WithValue("head", true),
27 | item.NewStack(item.Pickaxe{Tier: item.ToolTierDiamond}, 1).WithEnchantments(durability, efficiency),
28 | item.NewStack(item.Axe{Tier: item.ToolTierDiamond}, 1).WithEnchantments(durability, efficiency),
29 | item.NewStack(block.Planks{}, 64),
30 | item.NewStack(block.Cobblestone{}, 64),
31 | item.NewStack(item.Arrow{}, 64),
32 | item.NewStack(item.Bucket{Content: block.Water{}}, 1),
33 | item.NewStack(item.Bucket{Content: block.Water{}}, 1),
34 | item.NewStack(item.Bucket{Content: block.Lava{}}, 1),
35 | item.NewStack(item.Bucket{Content: block.Lava{}}, 1),
36 | }
37 | }
38 |
39 | // Armour ...
40 | func (BuildUHC) Armour(*player.Player) [4]item.Stack {
41 | durability := item.NewEnchantment(enchantment.Unbreaking{}, 10)
42 | return [4]item.Stack{
43 | item.NewStack(item.Helmet{Tier: item.ArmourTierDiamond}, 1).WithEnchantments(durability),
44 | item.NewStack(item.Chestplate{Tier: item.ArmourTierDiamond}, 1).WithEnchantments(durability),
45 | item.NewStack(item.Leggings{Tier: item.ArmourTierDiamond}, 1).WithEnchantments(durability),
46 | item.NewStack(item.Boots{Tier: item.ArmourTierDiamond}, 1).WithEnchantments(durability),
47 | }
48 | }
49 |
50 | // Effects ...
51 | func (BuildUHC) Effects(*player.Player) []effect.Effect {
52 | return []effect.Effect{effect.New(effect.Speed{}, 1, time.Hour*24).WithoutParticles()}
53 | }
54 |
--------------------------------------------------------------------------------
/vasar/game/kit/combo.go:
--------------------------------------------------------------------------------
1 | package kit
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/entity/effect"
5 | "github.com/df-mc/dragonfly/server/item"
6 | "github.com/df-mc/dragonfly/server/item/enchantment"
7 | "github.com/df-mc/dragonfly/server/item/potion"
8 | "github.com/df-mc/dragonfly/server/player"
9 | it "github.com/vasar-network/practice/vasar/item"
10 | )
11 |
12 | // Combo represents the kit given when players join Combo.
13 | type Combo struct{}
14 |
15 | // Items ...
16 | func (Combo) Items(*player.Player) [36]item.Stack {
17 | return [36]item.Stack{
18 | 0: item.NewStack(item.Sword{Tier: item.ToolTierDiamond}, 1).WithEnchantments(item.NewEnchantment(enchantment.Unbreaking{}, 5)),
19 | 1: item.NewStack(item.EnchantedApple{}, 32),
20 |
21 | 7: item.NewStack(it.Potion{Type: potion.Swiftness()}, 1),
22 | 8: item.NewStack(it.Potion{Type: potion.Swiftness()}, 1),
23 | }
24 | }
25 |
26 | // Armour ...
27 | func (Combo) Armour(*player.Player) [4]item.Stack {
28 | durability := item.NewEnchantment(enchantment.Unbreaking{}, 5)
29 | return [4]item.Stack{
30 | item.NewStack(item.Helmet{Tier: item.ArmourTierDiamond}, 1).WithEnchantments(durability),
31 | item.NewStack(item.Chestplate{Tier: item.ArmourTierDiamond}, 1).WithEnchantments(durability),
32 | item.NewStack(item.Leggings{Tier: item.ArmourTierDiamond}, 1).WithEnchantments(durability),
33 | item.NewStack(item.Boots{Tier: item.ArmourTierDiamond}, 1).WithEnchantments(durability),
34 | }
35 | }
36 |
37 | // Effects ...
38 | func (Combo) Effects(*player.Player) []effect.Effect {
39 | return []effect.Effect{}
40 | }
41 |
--------------------------------------------------------------------------------
/vasar/game/kit/debuff.go:
--------------------------------------------------------------------------------
1 | package kit
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/entity/effect"
5 | "github.com/df-mc/dragonfly/server/item"
6 | "github.com/df-mc/dragonfly/server/item/enchantment"
7 | "github.com/df-mc/dragonfly/server/item/potion"
8 | "github.com/df-mc/dragonfly/server/player"
9 | it "github.com/vasar-network/practice/vasar/item"
10 | )
11 |
12 | // Debuff represents the kit given when players join Debuff.
13 | type Debuff struct{}
14 |
15 | // Items ...
16 | func (Debuff) Items(*player.Player) [36]item.Stack {
17 | items := [36]item.Stack{
18 | item.NewStack(item.Sword{Tier: item.ToolTierDiamond}, 1).WithEnchantments(item.NewEnchantment(enchantment.Unbreaking{}, 10)),
19 | item.NewStack(it.VasarPearl{}, 16),
20 | }
21 | for i := 2; i < 36; i++ {
22 | items[i] = item.NewStack(it.VasarPotion{Type: potion.StrongHealing()}, 1)
23 | }
24 |
25 | items[9] = item.NewStack(item.SplashPotion{Type: potion.Poison()}, 1)
26 | items[18] = item.NewStack(item.SplashPotion{Type: potion.Poison()}, 1)
27 | items[27] = item.NewStack(item.SplashPotion{Type: potion.Poison()}, 1)
28 |
29 | items[10] = item.NewStack(item.SplashPotion{Type: potion.StrongSlowness()}, 1)
30 | items[19] = item.NewStack(item.SplashPotion{Type: potion.StrongSlowness()}, 1)
31 | items[28] = item.NewStack(item.SplashPotion{Type: potion.StrongSlowness()}, 1)
32 |
33 | items[2] = item.NewStack(it.Potion{Type: potion.Swiftness()}, 1)
34 | items[26] = item.NewStack(it.Potion{Type: potion.Swiftness()}, 1)
35 | items[35] = item.NewStack(it.Potion{Type: potion.Swiftness()}, 1)
36 | return items
37 | }
38 |
39 | // Armour ...
40 | func (Debuff) Armour(*player.Player) [4]item.Stack {
41 | durability := item.NewEnchantment(enchantment.Unbreaking{}, 10)
42 | return [4]item.Stack{
43 | item.NewStack(item.Helmet{Tier: item.ArmourTierDiamond}, 1).WithEnchantments(durability),
44 | item.NewStack(item.Chestplate{Tier: item.ArmourTierDiamond}, 1).WithEnchantments(durability),
45 | item.NewStack(item.Leggings{Tier: item.ArmourTierDiamond}, 1).WithEnchantments(durability),
46 | item.NewStack(item.Boots{Tier: item.ArmourTierDiamond}, 1).WithEnchantments(durability),
47 | }
48 | }
49 |
50 | // Effects ...
51 | func (Debuff) Effects(*player.Player) []effect.Effect {
52 | return []effect.Effect{}
53 | }
54 |
--------------------------------------------------------------------------------
/vasar/game/kit/gapple.go:
--------------------------------------------------------------------------------
1 | package kit
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/entity/effect"
5 | "github.com/df-mc/dragonfly/server/item"
6 | "github.com/df-mc/dragonfly/server/item/enchantment"
7 | "github.com/df-mc/dragonfly/server/item/potion"
8 | "github.com/df-mc/dragonfly/server/player"
9 | it "github.com/vasar-network/practice/vasar/item"
10 | )
11 |
12 | // Gapple represents the kit given when players join Gapple.
13 | type Gapple struct{}
14 |
15 | // Items ...
16 | func (Gapple) Items(*player.Player) [36]item.Stack {
17 | items := [36]item.Stack{
18 | item.NewStack(item.Sword{Tier: item.ToolTierDiamond}, 1).WithEnchantments(item.NewEnchantment(enchantment.Unbreaking{}, 10)),
19 | item.NewStack(item.GoldenApple{}, 16),
20 | }
21 |
22 | items[2] = item.NewStack(it.Potion{Type: potion.Swiftness()}, 1)
23 | items[26] = item.NewStack(it.Potion{Type: potion.Swiftness()}, 1)
24 | items[35] = item.NewStack(it.Potion{Type: potion.Swiftness()}, 1)
25 | return items
26 | }
27 |
28 | // Armour ...
29 | func (Gapple) Armour(*player.Player) [4]item.Stack {
30 | durability := item.NewEnchantment(enchantment.Unbreaking{}, 10)
31 | return [4]item.Stack{
32 | item.NewStack(item.Helmet{Tier: item.ArmourTierDiamond}, 1).WithEnchantments(durability),
33 | item.NewStack(item.Chestplate{Tier: item.ArmourTierDiamond}, 1).WithEnchantments(durability),
34 | item.NewStack(item.Leggings{Tier: item.ArmourTierDiamond}, 1).WithEnchantments(durability),
35 | item.NewStack(item.Boots{Tier: item.ArmourTierDiamond}, 1).WithEnchantments(durability),
36 | }
37 | }
38 |
39 | // Effects ...
40 | func (Gapple) Effects(*player.Player) []effect.Effect {
41 | return []effect.Effect{}
42 | }
43 |
--------------------------------------------------------------------------------
/vasar/game/kit/items.go:
--------------------------------------------------------------------------------
1 | package kit
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/item"
5 | "github.com/df-mc/dragonfly/server/player"
6 | "github.com/df-mc/dragonfly/server/session"
7 | "github.com/df-mc/dragonfly/server/world"
8 | "github.com/vasar-network/practice/vasar/game/healing"
9 | "github.com/vasar-network/vails"
10 |
11 | _ "unsafe"
12 | )
13 |
14 | // bed is a dummy item used for the queue kit.
15 | type bed struct{}
16 |
17 | // EncodeItem ...
18 | func (bed) EncodeItem() (name string, meta int16) {
19 | return "minecraft:bed", 14
20 | }
21 |
22 | // Apply ...
23 | func Apply(kit vails.Kit, p *player.Player) {
24 | p.Inventory().Clear()
25 | p.Armour().Clear()
26 |
27 | p.SetHeldItems(item.Stack{}, item.Stack{})
28 | if s := player_session(p); s != session.Nop {
29 | _ = s.SetHeldSlot(0)
30 | }
31 |
32 | p.StopSneaking()
33 | p.StopSwimming()
34 | p.StopSprinting()
35 | p.StopFlying()
36 | p.ResetFallDistance()
37 | p.SetGameMode(world.GameModeSurvival)
38 |
39 | p.Heal(20, healing.SourceKit{})
40 | p.SetFood(20)
41 | for _, eff := range p.Effects() {
42 | p.RemoveEffect(eff.Type())
43 | }
44 |
45 | inv := p.Inventory()
46 | armour := kit.Armour(p)
47 | for slot, it := range kit.Items(p) {
48 | _ = inv.SetItem(slot, it)
49 | }
50 | for _, eff := range kit.Effects(p) {
51 | p.AddEffect(eff)
52 | }
53 | p.Armour().Set(armour[0], armour[1], armour[2], armour[3])
54 | }
55 |
56 | // init registers the dummy items/enchantments.
57 | func init() {
58 | world.RegisterItem(bed{})
59 | }
60 |
61 | //go:linkname player_session github.com/df-mc/dragonfly/server/player.(*Player).session
62 | //noinspection ALL
63 | func player_session(*player.Player) *session.Session
64 |
--------------------------------------------------------------------------------
/vasar/game/kit/lobby.go:
--------------------------------------------------------------------------------
1 | package kit
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/entity/effect"
5 | "github.com/df-mc/dragonfly/server/item"
6 | "github.com/df-mc/dragonfly/server/player"
7 | "github.com/vasar-network/vails/lang"
8 | )
9 |
10 | // Lobby represents the kit given when players join the lobby.
11 | type Lobby struct{}
12 |
13 | // Items ...
14 | func (Lobby) Items(p *player.Player) [36]item.Stack {
15 | return [36]item.Stack{
16 | 0: item.NewStack(item.Sword{Tier: item.ToolTierDiamond}, 1).WithCustomName(
17 | "§r"+lang.Translatef(p.Locale(), "item.lobby.unranked"),
18 | ).WithValue("lobby", 0),
19 | 1: item.NewStack(item.Sword{Tier: item.ToolTierIron}, 1).WithCustomName(
20 | "§r"+lang.Translatef(p.Locale(), "item.lobby.ranked"),
21 | ).WithValue("lobby", 1),
22 | 2: item.NewStack(item.Sword{Tier: item.ToolTierStone}, 1).WithCustomName(
23 | "§r"+lang.Translatef(p.Locale(), "item.lobby.ffa"),
24 | ).WithValue("lobby", 2),
25 |
26 | 6: item.NewStack(item.NetherStar{}, 1).WithCustomName(
27 | "§r"+lang.Translatef(p.Locale(), "item.lobby.spectate"),
28 | ).WithValue("lobby", 3),
29 | 7: item.NewStack(item.NetherStar{}, 1).WithCustomName(
30 | "§r"+lang.Translatef(p.Locale(), "item.lobby.party"),
31 | ).WithValue("lobby", 4),
32 | 8: item.NewStack(item.EnchantedBook{}, 1).WithCustomName(
33 | "§r"+lang.Translatef(p.Locale(), "item.lobby.settings"),
34 | ).WithValue("lobby", 5),
35 | }
36 | }
37 |
38 | // Armour ...
39 | func (Lobby) Armour(*player.Player) [4]item.Stack {
40 | return [4]item.Stack{}
41 | }
42 |
43 | // Effects ...
44 | func (Lobby) Effects(*player.Player) []effect.Effect {
45 | return []effect.Effect{}
46 | }
47 |
--------------------------------------------------------------------------------
/vasar/game/kit/nodebuff.go:
--------------------------------------------------------------------------------
1 | package kit
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/entity/effect"
5 | "github.com/df-mc/dragonfly/server/item"
6 | "github.com/df-mc/dragonfly/server/item/enchantment"
7 | "github.com/df-mc/dragonfly/server/item/potion"
8 | "github.com/df-mc/dragonfly/server/player"
9 | it "github.com/vasar-network/practice/vasar/item"
10 | "time"
11 | )
12 |
13 | // NoDebuff represents the kit given when players join NoDebuff.
14 | type NoDebuff struct {
15 | // FFA returns true if the kit is for FFA, varying some options such as effects and potions.
16 | FFA bool
17 | }
18 |
19 | // Items ...
20 | func (n NoDebuff) Items(*player.Player) [36]item.Stack {
21 | items := [36]item.Stack{
22 | item.NewStack(item.Sword{Tier: item.ToolTierDiamond}, 1).WithEnchantments(item.NewEnchantment(enchantment.Unbreaking{}, 10)),
23 | item.NewStack(it.VasarPearl{}, 16),
24 | }
25 | for i := 2; i < 36; i++ {
26 | items[i] = item.NewStack(it.VasarPotion{Type: potion.StrongHealing()}, 1)
27 | }
28 |
29 | if !n.FFA {
30 | items[2] = item.NewStack(it.Potion{Type: potion.Swiftness()}, 1)
31 | items[26] = item.NewStack(it.Potion{Type: potion.Swiftness()}, 1)
32 | items[35] = item.NewStack(it.Potion{Type: potion.Swiftness()}, 1)
33 | }
34 | return items
35 | }
36 |
37 | // Armour ...
38 | func (NoDebuff) Armour(*player.Player) [4]item.Stack {
39 | durability := item.NewEnchantment(enchantment.Unbreaking{}, 10)
40 | return [4]item.Stack{
41 | item.NewStack(item.Helmet{Tier: item.ArmourTierDiamond}, 1).WithEnchantments(durability),
42 | item.NewStack(item.Chestplate{Tier: item.ArmourTierDiamond}, 1).WithEnchantments(durability),
43 | item.NewStack(item.Leggings{Tier: item.ArmourTierDiamond}, 1).WithEnchantments(durability),
44 | item.NewStack(item.Boots{Tier: item.ArmourTierDiamond}, 1).WithEnchantments(durability),
45 | }
46 | }
47 |
48 | // Effects ...
49 | func (n NoDebuff) Effects(*player.Player) []effect.Effect {
50 | if n.FFA {
51 | return []effect.Effect{effect.New(effect.Speed{}, 1, time.Hour*24).WithoutParticles()}
52 | }
53 | return []effect.Effect{}
54 | }
55 |
--------------------------------------------------------------------------------
/vasar/game/kit/queue.go:
--------------------------------------------------------------------------------
1 | package kit
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/entity/effect"
5 | "github.com/df-mc/dragonfly/server/item"
6 | "github.com/df-mc/dragonfly/server/player"
7 | )
8 |
9 | // Queue represents the kit given when players join queue.
10 | type Queue struct{}
11 |
12 | // Items ...
13 | func (Queue) Items(*player.Player) [36]item.Stack {
14 | return [36]item.Stack{item.NewStack(bed{}, 1).WithCustomName("§r§cLeave Queue").WithValue("queue", 0)}
15 | }
16 |
17 | // Armour ...
18 | func (Queue) Armour(*player.Player) [4]item.Stack {
19 | return [4]item.Stack{}
20 | }
21 |
22 | // Effects ...
23 | func (Queue) Effects(*player.Player) []effect.Effect {
24 | return []effect.Effect{}
25 | }
26 |
--------------------------------------------------------------------------------
/vasar/game/kit/soup.go:
--------------------------------------------------------------------------------
1 | package kit
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/entity/effect"
5 | "github.com/df-mc/dragonfly/server/item"
6 | "github.com/df-mc/dragonfly/server/item/enchantment"
7 | "github.com/df-mc/dragonfly/server/item/potion"
8 | "github.com/df-mc/dragonfly/server/player"
9 | it "github.com/vasar-network/practice/vasar/item"
10 | "time"
11 | )
12 |
13 | // Soup represents the kit given when players join Soup.
14 | type Soup struct {
15 | // FFA returns true if the kit is for FFA, varying some options such as effects and potions.
16 | FFA bool
17 | }
18 |
19 | // Items ...
20 | func (s Soup) Items(*player.Player) [36]item.Stack {
21 | items := [36]item.Stack{
22 | item.NewStack(item.Sword{Tier: item.ToolTierIron}, 1).WithEnchantments(item.NewEnchantment(enchantment.Sharpness{}, 1), item.NewEnchantment(enchantment.Unbreaking{}, 3)),
23 | }
24 | for i := 1; i < 36; i++ {
25 | items[i] = item.NewStack(it.Stew{}, 1)
26 | }
27 | if !s.FFA {
28 | items[1] = item.NewStack(it.Potion{Type: potion.Swiftness()}, 1)
29 | items[26] = item.NewStack(it.Potion{Type: potion.Swiftness()}, 1)
30 | items[35] = item.NewStack(it.Potion{Type: potion.Swiftness()}, 1)
31 | }
32 | return items
33 | }
34 |
35 | // Armour ...
36 | func (Soup) Armour(*player.Player) [4]item.Stack {
37 | durability := item.NewEnchantment(enchantment.Unbreaking{}, 3)
38 | protection := item.NewEnchantment(enchantment.Protection{}, 2)
39 | return [4]item.Stack{
40 | item.NewStack(item.Helmet{Tier: item.ArmourTierIron}, 1).WithEnchantments(durability, item.NewEnchantment(enchantment.Protection{}, 2)),
41 | item.NewStack(item.Chestplate{Tier: item.ArmourTierIron}, 1).WithEnchantments(durability, protection),
42 | item.NewStack(item.Leggings{Tier: item.ArmourTierIron}, 1).WithEnchantments(durability, protection),
43 | item.NewStack(item.Boots{Tier: item.ArmourTierIron}, 1).WithEnchantments(durability, protection),
44 | }
45 | }
46 |
47 | // Effects ...
48 | func (s Soup) Effects(*player.Player) []effect.Effect {
49 | if s.FFA {
50 | return []effect.Effect{effect.New(effect.Speed{}, 1, time.Hour*24).WithoutParticles()}
51 | }
52 | return []effect.Effect{}
53 | }
54 |
--------------------------------------------------------------------------------
/vasar/game/kit/stick_fight.go:
--------------------------------------------------------------------------------
1 | package kit
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/block"
5 | "github.com/df-mc/dragonfly/server/entity/effect"
6 | "github.com/df-mc/dragonfly/server/item"
7 | "github.com/df-mc/dragonfly/server/player"
8 | )
9 |
10 | // StickFight represents the kit given when players join Stick Fight.
11 | type StickFight struct{}
12 |
13 | // Items ...
14 | func (StickFight) Items(*player.Player) [36]item.Stack {
15 | return [36]item.Stack{
16 | item.NewStack(item.Stick{}, 1),
17 | item.NewStack(item.Shears{}, 1),
18 | item.NewStack(block.Wool{}, 6),
19 | }
20 | }
21 |
22 | // Armour ...
23 | func (StickFight) Armour(*player.Player) [4]item.Stack {
24 | return [4]item.Stack{
25 | item.NewStack(item.Helmet{Tier: item.ArmourTierLeather}, 1),
26 | item.NewStack(item.Chestplate{Tier: item.ArmourTierLeather}, 1),
27 | item.NewStack(item.Leggings{Tier: item.ArmourTierLeather}, 1),
28 | item.NewStack(item.Boots{Tier: item.ArmourTierLeather}, 1),
29 | }
30 | }
31 |
32 | // Effects ...
33 | func (s StickFight) Effects(*player.Player) []effect.Effect {
34 | return []effect.Effect{}
35 | }
36 |
--------------------------------------------------------------------------------
/vasar/game/kit/sumo.go:
--------------------------------------------------------------------------------
1 | package kit
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/entity/effect"
5 | "github.com/df-mc/dragonfly/server/item"
6 | "github.com/df-mc/dragonfly/server/player"
7 | )
8 |
9 | // Sumo represents the kit given when players join Sumo.
10 | type Sumo struct {
11 | // FFA returns true if the kit is for FFA, varying some options such as effects and potions.
12 | FFA bool
13 | }
14 |
15 | // Items ...
16 | func (Sumo) Items(*player.Player) [36]item.Stack {
17 | return [36]item.Stack{}
18 | }
19 |
20 | // Armour ...
21 | func (Sumo) Armour(*player.Player) [4]item.Stack {
22 | return [4]item.Stack{}
23 | }
24 |
25 | // Effects ...
26 | func (s Sumo) Effects(*player.Player) []effect.Effect {
27 | return []effect.Effect{}
28 | }
29 |
--------------------------------------------------------------------------------
/vasar/game/lobby/inventory_handler.go:
--------------------------------------------------------------------------------
1 | package lobby
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/event"
5 | "github.com/df-mc/dragonfly/server/item"
6 | "github.com/df-mc/dragonfly/server/item/inventory"
7 | )
8 |
9 | // inventoryHandler handles inventory related stuff.
10 | type inventoryHandler inventory.NopHandler
11 |
12 | // HandleTake ...
13 | func (h inventoryHandler) HandleTake(ctx *event.Context, _ int, _ item.Stack) {
14 | ctx.Cancel()
15 | }
16 |
17 | // HandlePlace ...
18 | func (h inventoryHandler) HandlePlace(ctx *event.Context, _ int, _ item.Stack) {
19 | ctx.Cancel()
20 | }
21 |
22 | // HandleDrop ...
23 | func (h inventoryHandler) HandleDrop(ctx *event.Context, _ int, _ item.Stack) {
24 | ctx.Cancel()
25 | }
26 |
--------------------------------------------------------------------------------
/vasar/game/lobby/provider.go:
--------------------------------------------------------------------------------
1 | package lobby
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/player"
5 | "github.com/df-mc/dragonfly/server/world"
6 | "github.com/vasar-network/practice/vasar/board"
7 | "github.com/vasar-network/practice/vasar/game/kit"
8 | "github.com/vasar-network/practice/vasar/user"
9 | "github.com/vasar-network/vails/sets"
10 | "sync"
11 | )
12 |
13 | // providers maps between a *player.Player and a *Provider.
14 | var providers sync.Map
15 |
16 | // LookupProvider looks up the *Provider of the *player.Player passed.
17 | func LookupProvider(p *player.Player) (*Provider, bool) {
18 | h, ok := providers.Load(p)
19 | if ok {
20 | return h.(*Provider), ok
21 | }
22 | return nil, false
23 | }
24 |
25 | // lobby ...
26 | var lobby *Provider
27 |
28 | // Lobby ...
29 | func Lobby() *Provider {
30 | return lobby
31 | }
32 |
33 | // Provider is the provider for the lobby.
34 | type Provider struct {
35 | w *world.World
36 |
37 | playerMu sync.Mutex
38 | players sets.Set[*player.Player]
39 | }
40 |
41 | // NewProvider creates a new lobby provider.
42 | func NewProvider(w *world.World) *Provider {
43 | lobby = &Provider{
44 | w: w,
45 | players: make(sets.Set[*player.Player]),
46 | }
47 | return lobby
48 | }
49 |
50 | // AddPlayer ...
51 | func (s *Provider) AddPlayer(p *player.Player) {
52 | providers.Store(p, s)
53 | p.Inventory().Handle(inventoryHandler{})
54 |
55 | kit.Apply(kit.Lobby{}, p)
56 |
57 | s.playerMu.Lock()
58 | s.players.Add(p)
59 | s.playerMu.Unlock()
60 |
61 | if p.World() != s.w {
62 | s.w.AddEntity(p)
63 | }
64 | p.Teleport(s.w.Spawn().Vec3Middle())
65 |
66 | if u, ok := user.Lookup(p); ok {
67 | yaw, pitch := p.Rotation()
68 | u.Rotate(180-yaw, -pitch)
69 |
70 | u.EnableProjectiles()
71 | if u.PearlCoolDown() {
72 | u.TogglePearlCoolDown()
73 | }
74 | if u.Tagged() {
75 | u.RemoveTag()
76 | }
77 | u.SetBoard(s)
78 | }
79 |
80 | players := s.Players()
81 | for _, otherP := range players {
82 | if otherU, ok := user.Lookup(otherP); ok {
83 | otherU.Board().SendScoreboard(otherP)
84 | }
85 | }
86 | }
87 |
88 | // RemovePlayer ...
89 | func (s *Provider) RemovePlayer(p *player.Player, force bool) {
90 | providers.Delete(p)
91 | p.Inventory().Handle(nil)
92 |
93 | s.playerMu.Lock()
94 | s.players.Delete(p)
95 | s.playerMu.Unlock()
96 |
97 | if u, ok := user.Lookup(p); ok {
98 | u.SetBoard(board.NopProvider{})
99 | }
100 | if !force {
101 | for _, otherP := range s.Players() {
102 | if otherU, ok := user.Lookup(otherP); ok {
103 | otherU.Board().SendScoreboard(otherP)
104 | }
105 | }
106 | }
107 | }
108 |
109 | // Players ...
110 | func (s *Provider) Players() []*player.Player {
111 | s.playerMu.Lock()
112 | defer s.playerMu.Unlock()
113 | return s.players.Values()
114 | }
115 |
116 | // PlayerCount ...
117 | func (s *Provider) PlayerCount() int {
118 | s.playerMu.Lock()
119 | defer s.playerMu.Unlock()
120 | return len(s.players)
121 | }
122 |
123 | // SendScoreboard ...
124 | func (s *Provider) SendScoreboard(p *player.Player) {
125 | users := user.Count()
126 | board.Send(p, "scoreboard.lobby", users, users-s.PlayerCount())
127 | }
128 |
--------------------------------------------------------------------------------
/vasar/game/match/doc.go:
--------------------------------------------------------------------------------
1 | package match
2 |
3 | // Package "match" is an absolute rushed shitstorm of code. Please do not reference this for any serious project.
4 | // ...the grid system is pretty cool though, in my opinion.
5 |
--------------------------------------------------------------------------------
/vasar/game/match/grid.go:
--------------------------------------------------------------------------------
1 | package match
2 |
3 | import (
4 | "sync"
5 | )
6 |
7 | // GridPos is the position of a node on the grid. It is composed of two integers.
8 | type GridPos [2]int
9 |
10 | // Add adds two GridPos' together.
11 | func (p GridPos) Add(o GridPos) GridPos {
12 | return GridPos{p[0] + o[0], p[1] + o[1]}
13 | }
14 |
15 | // X returns the X coordinate of the node position. It is equivalent to GridPos[0].
16 | func (p GridPos) X() int {
17 | return p[0]
18 | }
19 |
20 | // Y returns the Y coordinate of the node position. It is equivalent to GridPos[1].
21 | func (p GridPos) Y() int {
22 | return p[1]
23 | }
24 |
25 | // Grid is a grid that keeps track of arena placements on a 2D level. It contains all the open positions on the grid,
26 | // and the positions that are occupied by arenas.
27 | type Grid struct {
28 | nodeMu sync.Mutex
29 | nodes map[GridPos]struct{}
30 | }
31 |
32 | // NewGrid initializes a new grid with the provided step.
33 | func NewGrid() *Grid {
34 | return &Grid{nodes: map[GridPos]struct{}{}}
35 | }
36 |
37 | // Next returns the next open node position on the grid. The second return value will be false if no open
38 | // node can be found.
39 | func (g *Grid) Next() GridPos {
40 | g.nodeMu.Lock()
41 | defer g.nodeMu.Unlock()
42 |
43 | if len(g.nodes) == 0 {
44 | // Return the origin.
45 | return GridPos{}
46 | }
47 |
48 | var (
49 | pos GridPos
50 | l int
51 | d = GridPos{0, -1}
52 | )
53 | for {
54 | if _, ok := g.nodes[pos]; !ok {
55 | return pos
56 | }
57 |
58 | if pos[0] == pos[1] || (pos[0] < 0 && pos[0] == -pos[1]) || (pos[0] > 0 && pos[0] == 1-pos[1]) {
59 | l = d[0]
60 | d[0] = -d[1]
61 | d[1] = l
62 | }
63 |
64 | pos = pos.Add(d)
65 | }
66 | }
67 |
68 | // Close closes the node at the provided position.
69 | func (g *Grid) Close(pos GridPos) {
70 | g.nodeMu.Lock()
71 | defer g.nodeMu.Unlock()
72 | g.nodes[pos] = struct{}{}
73 | }
74 |
75 | // Open opens the node at the provided position.
76 | func (g *Grid) Open(pos GridPos) {
77 | g.nodeMu.Lock()
78 | defer g.nodeMu.Unlock()
79 | delete(g.nodes, pos)
80 | }
81 |
--------------------------------------------------------------------------------
/vasar/game/match/handler.go:
--------------------------------------------------------------------------------
1 | package match
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/event"
5 | "github.com/df-mc/dragonfly/server/player"
6 | "time"
7 | )
8 |
9 | // Handler handles events that are called by a match. Implementations of Handler may be used to listen to specific
10 | // events such as when a player quits (Warro) or when a match starts.
11 | type Handler interface {
12 | // HandlePrepare handles when a match is first initialized.
13 | HandlePrepare(duration *time.Duration)
14 | // HandleStart handles when a match starts.
15 | HandleStart(initial bool)
16 |
17 | // HandleScoreboardUpdate handles when the scoreboard is updated.
18 | HandleScoreboardUpdate(ctx *event.Context, p *player.Player)
19 |
20 | // HandleUserAdd handles when a user joins a match.
21 | HandleUserAdd(player *player.Player)
22 | // HandleUserStartHit handles when a player starts to hit another player. This is used to ensure a hit isn't logged
23 | // processed if it shouldn't be allowed.
24 | HandleUserStartHit(attacker, attacked *player.Player, hits int) bool
25 | // HandleUserHit handles when a player hits another player. This is useful for certain game types, such as boxing,
26 | // where a winner is determined by the amount of hits a player has.
27 | HandleUserHit(attacker, attacked *player.Player)
28 | // HandleUserRemove handles when a player is removed from a match. The forced boolean indicates if the player was
29 | // removed due to a timeout or a forced quit. This is useful for certain players, such as Warro.
30 | HandleUserRemove(ctx *event.Context, player *player.Player)
31 | }
32 |
33 | // NopHandler implements the Handler interface but does not execute any code when an event is called. The default handler
34 | // of matches is set to NopHandler. Users may embed NopHandler to avoid having to implement each method.
35 | type NopHandler struct{}
36 |
37 | // Compile time check to make sure NopHandler implements Handler.
38 | var _ Handler = (*NopHandler)(nil)
39 |
40 | func (NopHandler) HandlePrepare(*time.Duration) {}
41 | func (NopHandler) HandleStart(bool) {}
42 | func (NopHandler) HandleScoreboardUpdate(*event.Context, *player.Player) {}
43 | func (NopHandler) HandleUserAdd(*player.Player) {}
44 | func (NopHandler) HandleUserStartHit(*player.Player, *player.Player, int) bool { return true }
45 | func (NopHandler) HandleUserHit(*player.Player, *player.Player) {}
46 | func (NopHandler) HandleUserRemove(*event.Context, *player.Player) {}
47 |
--------------------------------------------------------------------------------
/vasar/game/match/hits_handler.go:
--------------------------------------------------------------------------------
1 | package match
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/event"
5 | "github.com/df-mc/dragonfly/server/player"
6 | "github.com/sandertv/gophertunnel/minecraft/text"
7 | "github.com/vasar-network/practice/vasar/board"
8 | "github.com/vasar-network/practice/vasar/user"
9 | "time"
10 | )
11 |
12 | // HitsHandler represents a handler to implement hit-based winning functionality for a match. This is used in certain
13 | // game-modes, such as boxing.
14 | type HitsHandler struct {
15 | NopHandler
16 |
17 | m *Match
18 |
19 | hitsToWin int
20 | }
21 |
22 | // NewHitsHandler creates a new hits handler.
23 | func NewHitsHandler(m *Match, hitsToWin int) *HitsHandler {
24 | if !m.duel {
25 | panic("hits handler: match must be a duel")
26 | }
27 | return &HitsHandler{m: m, hitsToWin: hitsToWin}
28 | }
29 |
30 | // HandlePrepare ...
31 | func (h *HitsHandler) HandlePrepare(duration *time.Duration) {
32 | *duration = time.Minute * 15
33 | }
34 |
35 | // HandleStart ...
36 | func (h *HitsHandler) HandleStart(initial bool) {
37 | if !initial {
38 | // Only send tips if this is the first start.
39 | return
40 | }
41 | for u := range h.m.players {
42 | u.Message("match.message.hits")
43 | }
44 | }
45 |
46 | // HandleScoreboardUpdate ...
47 | func (h *HitsHandler) HandleScoreboardUpdate(ctx *event.Context, p *player.Player) {
48 | h.m.mu.Lock()
49 | defer h.m.mu.Unlock()
50 |
51 | o := h.m.opponent(p)
52 |
53 | hitsA, hitsB := h.m.statistics[p].Hits, h.m.statistics[o].Hits
54 | diff := text.Colourf("(0)")
55 | if hitsA < hitsB {
56 | diff = text.Colourf("(-%d)", hitsB-hitsA)
57 | } else if hitsA > hitsB {
58 | diff = text.Colourf("(+%d)", hitsA-hitsB)
59 | }
60 |
61 | ctx.Cancel()
62 | board.Send(p,
63 | "scoreboard.duels.boxing",
64 | parseDuration(h.m.duration),
65 | diff,
66 | hitsA,
67 | hitsB,
68 | p.Latency().Milliseconds()*2,
69 | o.Latency().Milliseconds()*2,
70 | )
71 | }
72 |
73 | // surpassedHits represents the surpassed hits damage source, used primarily for boxing deaths.
74 | type surpassedHits struct{}
75 |
76 | // ReducedByArmour ...
77 | func (surpassedHits) ReducedByArmour() bool {
78 | return false
79 | }
80 |
81 | // ReducedByResistance ...
82 | func (surpassedHits) ReducedByResistance() bool {
83 | return false
84 | }
85 |
86 | // HandleUserStartHit ...
87 | func (h *HitsHandler) HandleUserStartHit(_, victim *player.Player, hits int) bool {
88 | if hits == h.hitsToWin {
89 | victim.Hurt(victim.MaxHealth(), surpassedHits{})
90 | return false
91 | }
92 | return true
93 | }
94 |
95 | // HandleUserHit ...
96 | func (h *HitsHandler) HandleUserHit(attacker, victim *player.Player) {
97 | if a, ok := user.Lookup(attacker); ok {
98 | a.Board().SendScoreboard(attacker)
99 | }
100 | if v, ok := user.Lookup(victim); ok {
101 | v.Board().SendScoreboard(victim)
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/vasar/game/match/rounds_handler.go:
--------------------------------------------------------------------------------
1 | package match
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/event"
5 | "github.com/df-mc/dragonfly/server/player"
6 | "github.com/df-mc/dragonfly/server/player/title"
7 | "github.com/vasar-network/practice/vasar/game/kit"
8 | "github.com/vasar-network/vails/lang"
9 | "time"
10 | )
11 |
12 | // RoundsHandler represents a handler to implement rounded functionality for a match.
13 | type RoundsHandler struct {
14 | NopHandler
15 |
16 | m *Match
17 |
18 | currentRound int
19 | totalRounds int
20 |
21 | statistics map[*player.Player]*struct {
22 | wins int
23 | losses int
24 | }
25 | }
26 |
27 | // NewRoundsHandler creates a new instance of a rounds handler.
28 | func NewRoundsHandler(m *Match, rounds int) *RoundsHandler {
29 | if !m.duel {
30 | panic("rounds handler: match must be a duel")
31 | }
32 | return &RoundsHandler{
33 | m: m,
34 | totalRounds: rounds,
35 | statistics: make(map[*player.Player]*struct {
36 | wins int
37 | losses int
38 | }),
39 | }
40 | }
41 |
42 | // HandlePrepare ...
43 | func (r *RoundsHandler) HandlePrepare(duration *time.Duration) {
44 | *duration = time.Minute * 15
45 | for u := range r.m.players {
46 | u.Player().SetImmobile()
47 | }
48 | }
49 |
50 | // HandleStart ...
51 | func (r *RoundsHandler) HandleStart(initial bool) {
52 | for u := range r.m.players {
53 | if initial {
54 | u.Message("match.message.rounds")
55 | } else {
56 | u.Message("round.message.start")
57 | }
58 | }
59 | }
60 |
61 | // HandleUserAdd ...
62 | func (r *RoundsHandler) HandleUserAdd(p *player.Player) {
63 | r.statistics[p] = &struct {
64 | wins int
65 | losses int
66 | }{}
67 | }
68 |
69 | // HandleUserRemove ...
70 | func (r *RoundsHandler) HandleUserRemove(ctx *event.Context, p *player.Player) {
71 | if ctx.Cancelled() {
72 | // Was cancelled, not much we can do.
73 | return
74 | }
75 |
76 | o := r.m.opponent(p)
77 | stats, opponentStats := r.statistics[p], r.statistics[o]
78 |
79 | stats.losses++
80 | opponentStats.wins++
81 | if opponentStats.wins < r.totalRounds {
82 | ctx.Cancel()
83 |
84 | r.m.startCount = time.Second * 4
85 | r.m.s = countDownState
86 | r.currentRound++
87 |
88 | t := title.New(lang.Translatef(p.Locale(), "round.title.lost")).WithFadeInDuration(0)
89 | t = t.WithSubtitle(lang.Translatef(p.Locale(), "round.subtitle.info", stats.wins, stats.losses))
90 | t = t.WithDuration(time.Second * 2).WithFadeOutDuration(time.Second)
91 | p.SendTitle(t)
92 |
93 | t = title.New(lang.Translatef(o.Locale(), "round.title.won")).WithFadeInDuration(0)
94 | t = t.WithSubtitle(lang.Translatef(o.Locale(), "round.subtitle.info", opponentStats.wins, opponentStats.losses))
95 | t = t.WithDuration(time.Second * 2).WithFadeOutDuration(time.Second)
96 | o.SendTitle(t)
97 |
98 | for pl := range r.m.players {
99 | kit.Apply(r.m.g.Kit(false), pl.Player())
100 | }
101 |
102 | p.SetAttackImmunity(r.m.startCount)
103 | o.SetAttackImmunity(r.m.startCount)
104 | p.SetImmobile()
105 | o.SetImmobile()
106 |
107 | r.m.ClearPlacements()
108 | r.m.teleport()
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/vasar/game/match/stats.go:
--------------------------------------------------------------------------------
1 | package match
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/item"
5 | )
6 |
7 | // Stats is a struct containing the statistics of a player.
8 | type Stats struct {
9 | // Hits is the number of hits the player dealt before the match ended.
10 | Hits int
11 | // Damage is the total damage the player dealt to opponents before the match ended.
12 | Damage float64
13 | // Items contains the items the player had before the match ended.
14 | Items []item.Stack
15 | }
16 |
--------------------------------------------------------------------------------
/vasar/game/match/structure.go:
--------------------------------------------------------------------------------
1 | package match
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/block"
5 | "github.com/df-mc/dragonfly/server/world"
6 | )
7 |
8 | // emptyStructure is an empty structure implementation, allowing areas to be cleared.
9 | type emptyStructure [3]int
10 |
11 | // Dimensions ...
12 | func (d emptyStructure) Dimensions() [3]int {
13 | return d
14 | }
15 |
16 | // At ...
17 | func (d emptyStructure) At(int, int, int, func(x int, y int, z int) world.Block) (world.Block, world.Liquid) {
18 | return block.Air{}, nil
19 | }
20 |
--------------------------------------------------------------------------------
/vasar/item/fishing_rod.go:
--------------------------------------------------------------------------------
1 | package item
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/item"
5 | "github.com/df-mc/dragonfly/server/item/creative"
6 | "github.com/df-mc/dragonfly/server/world"
7 | "time"
8 | )
9 |
10 | // FishingRod ...
11 | type FishingRod struct{}
12 |
13 | // init registers FishingRod as an item and adds it to the creative inventory.
14 | func init() {
15 | world.RegisterItem(FishingRod{})
16 | creative.RegisterItem(item.NewStack(FishingRod{}, 1))
17 | }
18 |
19 | func (FishingRod) DurabilityInfo() item.DurabilityInfo {
20 | return item.DurabilityInfo{
21 | MaxDurability: 355,
22 | BrokenItem: func() item.Stack { return item.Stack{} },
23 | }
24 | }
25 |
26 | // MaxCount ...
27 | func (FishingRod) MaxCount() int {
28 | return 1
29 | }
30 |
31 | // Rod ...
32 | func (FishingRod) Rod() bool {
33 | return true
34 | }
35 |
36 | // Cooldown ...
37 | func (FishingRod) Cooldown() time.Duration {
38 | return time.Second
39 | }
40 |
41 | // Use ...
42 | func (FishingRod) Use(_ *world.World, _ item.User, ctx *item.UseContext) bool {
43 | ctx.DamageItem(1)
44 | return true
45 | }
46 |
47 | // EncodeItem ...
48 | func (FishingRod) EncodeItem() (name string, meta int16) {
49 | return "minecraft:fishing_rod", 0
50 | }
51 |
--------------------------------------------------------------------------------
/vasar/item/potion.go:
--------------------------------------------------------------------------------
1 | package item
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/item"
5 | "github.com/df-mc/dragonfly/server/item/potion"
6 | "github.com/df-mc/dragonfly/server/world"
7 | "time"
8 | )
9 |
10 | // Potion is an item that grants effects on consumption.
11 | type Potion struct {
12 | // Type is the type of potion.
13 | Type potion.Potion
14 | }
15 |
16 | // MaxCount ...
17 | func (p Potion) MaxCount() int {
18 | return 1
19 | }
20 |
21 | // AlwaysConsumable ...
22 | func (p Potion) AlwaysConsumable() bool {
23 | return true
24 | }
25 |
26 | // ConsumeDuration ...
27 | func (p Potion) ConsumeDuration() time.Duration {
28 | return item.DefaultConsumeDuration
29 | }
30 |
31 | // Consume ...
32 | func (p Potion) Consume(_ *world.World, c item.Consumer) item.Stack {
33 | for _, effect := range p.Type.Effects() {
34 | c.AddEffect(effect.WithoutParticles())
35 | }
36 | return item.NewStack(item.GlassBottle{}, 1)
37 | }
38 |
39 | // EncodeItem ...
40 | func (p Potion) EncodeItem() (name string, meta int16) {
41 | return "minecraft:potion", int16(p.Type.Uint8())
42 | }
43 |
--------------------------------------------------------------------------------
/vasar/item/stew.go:
--------------------------------------------------------------------------------
1 | package item
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/entity"
5 | "github.com/df-mc/dragonfly/server/item"
6 | "github.com/df-mc/dragonfly/server/world"
7 | "github.com/vasar-network/practice/vasar/game/healing"
8 | )
9 |
10 | // Stew is a food item.
11 | type Stew struct{}
12 |
13 | // MaxCount ...
14 | func (Stew) MaxCount() int {
15 | return 1
16 | }
17 |
18 | // Use ...
19 | func (s Stew) Use(_ *world.World, user item.User, ctx *item.UseContext) bool {
20 | living, ok := user.(entity.Living)
21 | if ok && living.Health() < living.MaxHealth() {
22 | living.Heal(7, healing.SourceStew{})
23 | ctx.SubtractFromCount(1)
24 | }
25 | return ok
26 | }
27 |
28 | // EncodeItem ...
29 | func (Stew) EncodeItem() (name string, meta int16) {
30 | return "minecraft:mushroom_stew", 0
31 | }
32 |
--------------------------------------------------------------------------------
/vasar/item/vasar_pearl.go:
--------------------------------------------------------------------------------
1 | package item
2 |
3 | import (
4 | _ "embed"
5 | "github.com/df-mc/dragonfly/server/entity"
6 | "github.com/df-mc/dragonfly/server/item"
7 | "github.com/df-mc/dragonfly/server/world"
8 | "github.com/df-mc/dragonfly/server/world/sound"
9 | ent "github.com/vasar-network/practice/vasar/entity"
10 | "time"
11 | )
12 |
13 | // VasarPearl is an edited item for ender pearls.
14 | type VasarPearl struct{}
15 |
16 | // Use ...
17 | func (VasarPearl) Use(w *world.World, user item.User, ctx *item.UseContext) bool {
18 | yaw, pitch := user.Rotation()
19 | e := ent.NewEnderPearl(entity.EyePosition(user), entity.DirectionVector(user).Mul(2.3), yaw, pitch, user)
20 | w.AddEntity(e)
21 |
22 | w.PlaySound(user.Position(), sound.ItemThrow{})
23 | ctx.SubtractFromCount(1)
24 | return true
25 | }
26 |
27 | // Cooldown ...
28 | func (VasarPearl) Cooldown() time.Duration {
29 | return time.Second
30 | }
31 |
32 | // MaxCount ...
33 | func (VasarPearl) MaxCount() int {
34 | return 16
35 | }
36 |
37 | // EncodeItem ...
38 | func (VasarPearl) EncodeItem() (name string, meta int16) {
39 | return "minecraft:ender_pearl", 0
40 | }
41 |
--------------------------------------------------------------------------------
/vasar/item/vasar_potion.go:
--------------------------------------------------------------------------------
1 | package item
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/entity"
5 | "github.com/df-mc/dragonfly/server/item"
6 | "github.com/df-mc/dragonfly/server/item/potion"
7 | "github.com/df-mc/dragonfly/server/player"
8 | "github.com/df-mc/dragonfly/server/world"
9 | "github.com/df-mc/dragonfly/server/world/sound"
10 | ent "github.com/vasar-network/practice/vasar/entity"
11 | )
12 |
13 | // VasarPotion is an edited item for splash potions.
14 | type VasarPotion struct {
15 | // Type is the type of splash potion.
16 | Type potion.Potion
17 | }
18 |
19 | // Use ...
20 | func (v VasarPotion) Use(w *world.World, user item.User, ctx *item.UseContext) bool {
21 | force := 0.45
22 | debuff := shouldDebuff(v.Type)
23 | if debuff {
24 | force = 0.65
25 | }
26 |
27 | if p, ok := user.(*player.Player); ok && p.Sprinting() {
28 | force += 0.05
29 | }
30 |
31 | yaw, pitch := user.Rotation()
32 | e := ent.NewSplashPotion(entity.EyePosition(user), entity.DirectionVector(user).Mul(force), yaw, pitch, v.Type, debuff, user)
33 | w.AddEntity(e)
34 |
35 | w.PlaySound(user.Position(), sound.ItemThrow{})
36 | ctx.SubtractFromCount(1)
37 | return true
38 | }
39 |
40 | // MaxCount ...
41 | func (v VasarPotion) MaxCount() int {
42 | return 1
43 | }
44 |
45 | // EncodeItem ...
46 | func (v VasarPotion) EncodeItem() (name string, meta int16) {
47 | return "minecraft:splash_potion", int16(v.Type.Uint8())
48 | }
49 |
50 | // shouldDebuff returns true if the potion is a debuff potion.
51 | func shouldDebuff(p potion.Potion) bool {
52 | switch p {
53 | case potion.Slowness(), potion.LongSlowness(), potion.StrongSlowness(), potion.Harming(), potion.StrongHarming(),
54 | potion.Poison(), potion.LongPoison(), potion.StrongPoison(), potion.Weakness(), potion.LongWeakness(),
55 | potion.Wither(), potion.TurtleMaster(), potion.LongTurtleMaster(), potion.StrongTurtleMaster():
56 | return true
57 | }
58 | return false
59 | }
60 |
--------------------------------------------------------------------------------
/vasar/leaderboard.go:
--------------------------------------------------------------------------------
1 | package vasar
2 |
3 | import (
4 | "fmt"
5 | "github.com/brandenc40/romannumeral"
6 | "github.com/df-mc/dragonfly/server/entity"
7 | "github.com/go-gl/mathgl/mgl64"
8 | "github.com/sandertv/gophertunnel/minecraft/text"
9 | "github.com/vasar-network/practice/vasar/data"
10 | "github.com/vasar-network/practice/vasar/game"
11 | "math"
12 | "strings"
13 | "time"
14 | )
15 |
16 | // startLeaderboards spawns and starts updating lobby leaderboards. I sincerely apologize for the horrible code.
17 | func (v *Vasar) startLeaderboards() {
18 | b := entity.NewText("", mgl64.Vec3{-6.5, 60, -15.5})
19 | v.srv.World().AddEntity(b)
20 |
21 | leaderboards := []string{"global"}
22 | for _, g := range game.Games() {
23 | leaderboards = append(leaderboards, g.String())
24 | }
25 | leaderboards = append(leaderboards, "wins", "playtime")
26 |
27 | c := make(chan struct{}, 1)
28 | c <- struct{}{}
29 |
30 | var cursor int
31 | t := time.NewTicker(time.Second * 3)
32 | defer t.Stop()
33 |
34 | for {
35 | select {
36 | case <-v.c:
37 | return
38 | case <-c:
39 | variant := leaderboards[cursor]
40 |
41 | sb := &strings.Builder{}
42 | sb.WriteString(text.Colourf("TOP %v\n", strings.ReplaceAll(strings.ToUpper(variant), "_", " ")))
43 |
44 | var query string
45 | switch variant {
46 | case "global":
47 | query = "-practice.elo"
48 | case "wins":
49 | query = "-practice.ranked_wins"
50 | case "playtime":
51 | query = "-playtime"
52 | default:
53 | query = fmt.Sprintf("-practice.game_elo.%v", variant)
54 | }
55 |
56 | leaders, err := data.OrderedOfflineUsers(query, 10)
57 | if err != nil {
58 | panic(err)
59 | }
60 |
61 | for i, leader := range leaders {
62 | var value any
63 | switch variant {
64 | case "global":
65 | value = leader.Stats.Elo
66 | case "wins":
67 | value = leader.Stats.RankedWins
68 | case "playtime":
69 | value = fmt.Sprintf("%v hours", int(math.Floor(leader.PlayTime().Hours())))
70 | default:
71 | value = leader.Stats.GameElo[variant]
72 | }
73 |
74 | position, _ := romannumeral.IntToString(i + 1)
75 | sb.WriteString(text.Colourf(
76 | "%v. %v - %v\n",
77 | position,
78 | leader.DisplayName(),
79 | value,
80 | ))
81 | }
82 |
83 | cursor++
84 | if cursor == len(leaderboards) {
85 | cursor = 0
86 | }
87 |
88 | b.SetText(sb.String())
89 | case <-t.C:
90 | c <- struct{}{}
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/vasar/module/click.go:
--------------------------------------------------------------------------------
1 | package module
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/event"
5 | "github.com/df-mc/dragonfly/server/player"
6 | "github.com/df-mc/dragonfly/server/world"
7 | "github.com/sandertv/gophertunnel/minecraft/text"
8 | "github.com/vasar-network/practice/vasar/user"
9 | "sync"
10 | "time"
11 | )
12 |
13 | // Click is a handler that is used to track player clicks for CPS.
14 | type Click struct {
15 | player.NopHandler
16 |
17 | u *user.User
18 |
19 | clicks []time.Time
20 | clickMu sync.Mutex
21 | }
22 |
23 | // NewClick ...
24 | func NewClick(u *user.User) *Click {
25 | return &Click{u: u}
26 | }
27 |
28 | // HandlePunchAir ...
29 | func (c *Click) HandlePunchAir(*event.Context) {
30 | c.Click()
31 | }
32 |
33 | // HandleAttackEntity ...
34 | func (c *Click) HandleAttackEntity(*event.Context, world.Entity, *float64, *float64, *bool) {
35 | c.Click()
36 | }
37 |
38 | // Click adds a click to the click history.
39 | func (c *Click) Click() {
40 | c.clickMu.Lock()
41 | c.clicks = append(c.clicks, time.Now())
42 | if len(c.clicks) >= 100 {
43 | c.clicks = c.clicks[1:]
44 | }
45 | cps := c.calculate()
46 | c.clickMu.Unlock()
47 | for _, w := range c.u.ClickWatchers() {
48 | w.Player().SendTip(text.Colourf("%v CPS", cps))
49 | }
50 | if _, ok := c.u.WatchingClicks(); !ok && c.u.Settings().Display.CPS {
51 | c.u.Player().SendTip(text.Colourf("%v CPS", cps))
52 | }
53 | }
54 |
55 | // CPS returns the current clicks per second.
56 | func (c *Click) CPS() int {
57 | c.clickMu.Lock()
58 | defer c.clickMu.Unlock()
59 | return c.calculate()
60 | }
61 |
62 | // calculate uses the click samples to calculate the player's current clicks per second. This does not lock the click
63 | // mutex, as it is expected to only be called by CPS and Click.
64 | func (c *Click) calculate() int {
65 | var clicks int
66 | for _, past := range c.clicks {
67 | if time.Since(past) <= time.Second {
68 | clicks++
69 | }
70 | }
71 | return clicks
72 | }
73 |
--------------------------------------------------------------------------------
/vasar/module/combat.go:
--------------------------------------------------------------------------------
1 | package module
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/cmd"
5 | "github.com/df-mc/dragonfly/server/entity/damage"
6 | "github.com/df-mc/dragonfly/server/event"
7 | "github.com/df-mc/dragonfly/server/player"
8 | "github.com/vasar-network/practice/vasar/game/ffa"
9 | "github.com/vasar-network/practice/vasar/user"
10 | "github.com/vasar-network/vails/role"
11 | "golang.org/x/exp/slices"
12 | "time"
13 | )
14 |
15 | // Combat is a handler that is used for combat related things, such as the combat tag.
16 | type Combat struct {
17 | player.NopHandler
18 |
19 | u *user.User
20 | }
21 |
22 | // bannedCommands is a list of commands disallowed in combat.
23 | var bannedCommands = []string{"spawn", "rekit"}
24 |
25 | // NewCombat ...
26 | func NewCombat(u *user.User) *Combat {
27 | return &Combat{u: u}
28 | }
29 |
30 | // HandleCommandExecution ...
31 | func (c *Combat) HandleCommandExecution(ctx *event.Context, cmd cmd.Command, _ []string) {
32 | if c.u.Tagged() && !c.u.Roles().Contains(role.Operator{}) && slices.Contains(bannedCommands, cmd.Name()) {
33 | c.u.Message("user.feature.disabled")
34 | ctx.Cancel()
35 | }
36 | }
37 |
38 | // HandleHurt ...
39 | func (c *Combat) HandleHurt(ctx *event.Context, _ *float64, _ *time.Duration, s damage.Source) {
40 | if ctx.Cancelled() {
41 | // Was cancelled at some point, so just ignore this.
42 | return
43 | }
44 |
45 | var attacker *player.Player
46 | if a, ok := s.(damage.SourceEntityAttack); ok {
47 | if p, ok := a.Attacker.(*player.Player); ok {
48 | attacker = p
49 | }
50 | } else if t, ok := s.(damage.SourceProjectile); ok {
51 | if p, ok := t.Owner.(*player.Player); ok {
52 | attacker = p
53 | }
54 | }
55 | if attacker == nil {
56 | // No attacker, so we don't need to do anything.
57 | return
58 | }
59 |
60 | if a, ok := user.Lookup(attacker); ok {
61 | _, notify := ffa.LookupProvider(a.Player())
62 | a.Tag(c.u.Player(), false, notify)
63 |
64 | _, notify = ffa.LookupProvider(c.u.Player())
65 | c.u.Tag(attacker, false, notify)
66 | }
67 | }
68 |
69 | // HandleDeath ...
70 | func (c *Combat) HandleDeath(s damage.Source) {
71 | if c.u.Tagged() {
72 | c.u.RemoveTag()
73 | }
74 | src, ok := s.(damage.SourceEntityAttack)
75 | if !ok {
76 | // Not an entity attack, so we don't care.
77 | return
78 | }
79 | if p, ok := src.Attacker.(*player.Player); ok {
80 | if u, ok := user.Lookup(p); ok {
81 | _, notify := ffa.LookupProvider(p)
82 | u.Tag(nil, true, notify)
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/vasar/module/inventory.go:
--------------------------------------------------------------------------------
1 | package module
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/block/cube"
5 | "github.com/df-mc/dragonfly/server/entity/effect"
6 | "github.com/df-mc/dragonfly/server/event"
7 | "github.com/df-mc/dragonfly/server/item"
8 | "github.com/df-mc/dragonfly/server/player"
9 | "github.com/go-gl/mathgl/mgl64"
10 | "github.com/sandertv/gophertunnel/minecraft/text"
11 | "github.com/vasar-network/practice/vasar/form"
12 | "github.com/vasar-network/practice/vasar/game/match"
13 | "github.com/vasar-network/practice/vasar/user"
14 | "github.com/vasar-network/vails/role"
15 | "time"
16 | )
17 |
18 | // Inventory is a module that adds all items required to access basic functions outside the lobby.
19 | type Inventory struct {
20 | player.NopHandler
21 |
22 | u *user.User
23 | }
24 |
25 | // NewInventory ...
26 | func NewInventory(u *user.User) *Inventory {
27 | return &Inventory{u: u}
28 | }
29 |
30 | // HandleItemUseOnBlock ...
31 | func (i *Inventory) HandleItemUseOnBlock(ctx *event.Context, _ cube.Pos, _ cube.Face, _ mgl64.Vec3) {
32 | p := i.u.Player()
33 | h, _ := p.HeldItems()
34 | if _, ok := h.Value("queue"); ok {
35 | if prov, ok := match.LookupProvider(p); ok {
36 | prov.ExitQueue(p)
37 | ctx.Cancel()
38 | }
39 | }
40 | }
41 |
42 | // HandleItemUse ...
43 | func (i *Inventory) HandleItemUse(*event.Context) {
44 | p := i.u.Player()
45 | h, _ := p.HeldItems()
46 | if request, ok := h.Value("lobby"); ok {
47 | switch request {
48 | case 0:
49 | p.SendForm(form.NewUnrankedDuels())
50 | case 1:
51 | if !i.u.Roles().Contains(role.Plus{}, role.Operator{}) {
52 | if stats := i.u.Stats(); stats.UnrankedWins < 10 {
53 | i.u.Message("ranked.locked", 10-stats.UnrankedWins)
54 | return
55 | }
56 | }
57 | p.SendForm(form.NewRankedDuels())
58 | case 2:
59 | p.SendForm(form.NewFFA())
60 | case 3:
61 | p.SendForm(form.NewSpectate())
62 | case 5:
63 | p.SendForm(form.NewSettings(i.u))
64 | default:
65 | p.Message(text.Colourf("This action isn't implemented yet."))
66 | }
67 | } else if _, ok = h.Value("queue"); ok {
68 | if prov, ok := match.LookupProvider(p); ok {
69 | prov.ExitQueue(p)
70 | }
71 | } else if _, ok := h.Value("stats"); ok {
72 | p.SendForm(form.NewPostMatchStats(i.u))
73 | }
74 | }
75 |
76 | // HandleItemConsume ...
77 | func (i *Inventory) HandleItemConsume(_ *event.Context, h item.Stack) {
78 | if _, ok := h.Value("head"); ok {
79 | i.u.Player().AddEffect(effect.New(effect.Regeneration{}, 3, time.Second*9))
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/vasar/module/moderation.go:
--------------------------------------------------------------------------------
1 | package module
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/cmd"
5 | "github.com/df-mc/dragonfly/server/entity/damage"
6 | "github.com/df-mc/dragonfly/server/event"
7 | "github.com/df-mc/dragonfly/server/player"
8 | "github.com/df-mc/dragonfly/server/world"
9 | "github.com/vasar-network/practice/vasar/user"
10 | "time"
11 | )
12 |
13 | // Moderation is a module that handles moderation actions.
14 | type Moderation struct {
15 | player.NopHandler
16 |
17 | u *user.User
18 | }
19 |
20 | // NewModeration creates a new moderation module.
21 | func NewModeration(u *user.User) *Moderation {
22 | return &Moderation{u: u}
23 | }
24 |
25 | // HandleJoin ...
26 | func (m *Moderation) HandleJoin() {
27 | for _, u := range user.All() {
28 | if u.Vanished() {
29 | m.u.Player().HideEntity(u.Player())
30 | }
31 | }
32 | }
33 |
34 | // HandleCommandExecution ...
35 | func (m *Moderation) HandleCommandExecution(ctx *event.Context, _ cmd.Command, _ []string) {
36 | if m.u.Frozen() {
37 | m.u.Message("command.usage.frozen")
38 | ctx.Cancel()
39 | }
40 | }
41 |
42 | // HandleHurt ...
43 | func (m *Moderation) HandleHurt(ctx *event.Context, _ *float64, _ *time.Duration, _ damage.Source) {
44 | if m.u.Frozen() {
45 | ctx.Cancel()
46 | }
47 | }
48 |
49 | // HandleItemUse ...
50 | func (m *Moderation) HandleItemUse(ctx *event.Context) {
51 | if m.u.Frozen() {
52 | ctx.Cancel()
53 | }
54 | }
55 |
56 | // HandleAttackEntity ...
57 | func (m *Moderation) HandleAttackEntity(ctx *event.Context, _ world.Entity, _, _ *float64, _ *bool) {
58 | if m.u.Frozen() {
59 | ctx.Cancel()
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/vasar/module/protection.go:
--------------------------------------------------------------------------------
1 | package module
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/block/cube"
5 | "github.com/df-mc/dragonfly/server/entity/damage"
6 | "github.com/df-mc/dragonfly/server/event"
7 | "github.com/df-mc/dragonfly/server/item"
8 | "github.com/df-mc/dragonfly/server/player"
9 | "github.com/df-mc/dragonfly/server/world"
10 | "github.com/vasar-network/practice/vasar/game/lobby"
11 | "github.com/vasar-network/practice/vasar/game/match"
12 | "time"
13 | )
14 |
15 | // Protection is a module that ensures that players cannot break blocks or attack players in the lobby.
16 | type Protection struct {
17 | player.NopHandler
18 |
19 | p *player.Player
20 | }
21 |
22 | // NewProtection ...
23 | func NewProtection(p *player.Player) *Protection {
24 | return &Protection{p: p}
25 | }
26 |
27 | // HandleAttackEntity ...
28 | func (p *Protection) HandleAttackEntity(ctx *event.Context, _ world.Entity, _ *float64, _ *float64, _ *bool) {
29 | if _, ok := lobby.LookupProvider(p.p); ok {
30 | ctx.Cancel()
31 | }
32 | }
33 |
34 | // HandleHurt ...
35 | func (p *Protection) HandleHurt(ctx *event.Context, _ *float64, _ *time.Duration, s damage.Source) {
36 | if _, ok := lobby.LookupProvider(p.p); ok || (s == damage.SourceFall{}) {
37 | ctx.Cancel()
38 | }
39 | }
40 |
41 | // HandleFoodLoss ...
42 | func (*Protection) HandleFoodLoss(ctx *event.Context, _ int, _ int) {
43 | ctx.Cancel()
44 | }
45 |
46 | // HandleBlockPlace ...
47 | func (p *Protection) HandleBlockPlace(ctx *event.Context, pos cube.Pos, _ world.Block) {
48 | if m, ok := match.Lookup(p.p); ok && cube.PosFromVec3(m.Center()).Y()+5 > pos.Y() && m.LogPlacement(pos) {
49 | // Placement log was successful, don't cancel.
50 | return
51 | }
52 | ctx.Cancel()
53 | }
54 |
55 | // HandleBlockBreak ...
56 | func (p *Protection) HandleBlockBreak(ctx *event.Context, pos cube.Pos, _ *[]item.Stack) {
57 | if m, ok := match.Lookup(p.p); ok && m.LoggedPlacement(pos) {
58 | // Placement was logged, so we can allow this break.
59 | return
60 | }
61 | ctx.Cancel()
62 | }
63 |
--------------------------------------------------------------------------------
/vasar/module/rods.go:
--------------------------------------------------------------------------------
1 | package module
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/event"
5 | "github.com/df-mc/dragonfly/server/player"
6 | it "github.com/vasar-network/practice/vasar/item"
7 | "github.com/vasar-network/practice/vasar/user"
8 | )
9 |
10 | // Rods is a module providing functionality for fishing rods.
11 | type Rods struct {
12 | player.NopHandler
13 |
14 | u *user.User
15 | }
16 |
17 | // NewRods ...
18 | func NewRods(u *user.User) *Rods {
19 | return &Rods{u: u}
20 | }
21 |
22 | // HandleItemUse ...
23 | func (r *Rods) HandleItemUse(_ *event.Context) {
24 | p := r.u.Player()
25 | held, _ := p.HeldItems()
26 | if i, ok := held.Item().(it.FishingRod); ok && !p.HasCooldown(i) {
27 | r.u.ToggleRod()
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/vasar/module/settings.go:
--------------------------------------------------------------------------------
1 | package module
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/entity/damage"
5 | "github.com/df-mc/dragonfly/server/event"
6 | "github.com/df-mc/dragonfly/server/player"
7 | "github.com/df-mc/dragonfly/server/player/skin"
8 | "github.com/df-mc/dragonfly/server/world"
9 | "github.com/go-gl/mathgl/mgl64"
10 | "github.com/vasar-network/practice/vasar/game/ffa"
11 | "github.com/vasar-network/practice/vasar/game/healing"
12 | "github.com/vasar-network/practice/vasar/game/kit"
13 | "github.com/vasar-network/practice/vasar/user"
14 | "github.com/vasar-network/vails/cape"
15 | "math"
16 | )
17 |
18 | // Settings is a module which handles player settings.
19 | type Settings struct {
20 | player.NopHandler
21 |
22 | u *user.User
23 | }
24 |
25 | // NewSettings ...
26 | func NewSettings(u *user.User) *Settings {
27 | return &Settings{u: u}
28 | }
29 |
30 | // HandleJoin ...
31 | func (s *Settings) HandleJoin() {
32 | c, _ := cape.ByName(s.u.Settings().Advanced.Cape)
33 | sk := s.u.Player().Skin()
34 | sk.Cape = c.Cape()
35 | s.u.Player().SetSkin(sk)
36 | }
37 |
38 | // HandleSkinChange ...
39 | func (s *Settings) HandleSkinChange(_ *event.Context, sk *skin.Skin) {
40 | c, _ := cape.ByName(s.u.Settings().Advanced.Cape)
41 | (*sk).Cape = c.Cape()
42 | }
43 |
44 | // HandleMove ...
45 | func (s *Settings) HandleMove(_ *event.Context, pos mgl64.Vec3, newYaw, _ float64) {
46 | p := s.u.Player()
47 | if !s.u.Settings().Gameplay.ToggleSprint || p.Sprinting() {
48 | return
49 | }
50 | delta := pos.Sub(p.Position())
51 | if mgl64.FloatEqual(delta[0], 0) && mgl64.FloatEqual(delta[2], 0) {
52 | return
53 | }
54 | diff := (mgl64.RadToDeg(math.Atan2(delta[2], delta[0])) - 90) - newYaw
55 | if diff < 0 {
56 | diff += 360
57 | }
58 | if diff <= 65 && diff >= -65 {
59 | p.StartSprinting()
60 | }
61 | }
62 |
63 | // HandleDeath ...
64 | func (s *Settings) HandleDeath(src damage.Source) {
65 | if source, ok := src.(damage.SourceEntityAttack); ok {
66 | if p, ok := source.Attacker.(*player.Player); ok {
67 | u, ok := user.Lookup(p)
68 | if !ok {
69 | return
70 | }
71 | if prov, ok := ffa.LookupProvider(p); ok {
72 | u.Player().Heal(20, healing.SourceKill{})
73 | if u.Settings().Gameplay.AutoReapplyKit {
74 | kit.Apply(prov.Game().Kit(true), p)
75 | }
76 | }
77 | }
78 | }
79 | }
80 |
81 | // HandleAttackEntity ...
82 | func (s *Settings) HandleAttackEntity(ctx *event.Context, e world.Entity, _, _ *float64, _ *bool) {
83 | if ctx.Cancelled() {
84 | return
85 | }
86 | if e, ok := e.(*player.Player); ok && !e.AttackImmune() {
87 | s.u.MultiplyParticles(e, s.u.Settings().Advanced.ParticleMultiplier)
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/vasar/pearl_handler.go:
--------------------------------------------------------------------------------
1 | package vasar
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/event"
5 | "github.com/df-mc/dragonfly/server/player"
6 | "github.com/df-mc/dragonfly/server/session"
7 | "github.com/go-gl/mathgl/mgl32"
8 | "github.com/go-gl/mathgl/mgl64"
9 | "github.com/sandertv/gophertunnel/minecraft/protocol/packet"
10 | "github.com/vasar-network/practice/vasar/user"
11 |
12 | _ "unsafe"
13 | )
14 |
15 | // PearlHandler ...
16 | type PearlHandler struct{}
17 |
18 | // HandleTeleport ...
19 | func (PearlHandler) HandleTeleport(ctx *event.Context, p *player.Player, pos mgl64.Vec3) {
20 | if u, ok := user.Lookup(p); ok && u.Settings().Visual.PearlAnimation {
21 | ctx.Cancel()
22 |
23 | yaw, pitch := p.Rotation()
24 | session_writePacket(player_session(p), &packet.MovePlayer{
25 | EntityRuntimeID: 1,
26 | Position: mgl32.Vec3{float32(pos[0]), float32(pos[1] + 1.621), float32(pos[2])},
27 | Pitch: float32(pitch),
28 | Yaw: float32(yaw),
29 | HeadYaw: float32(yaw),
30 | Mode: packet.MoveModeNormal,
31 | })
32 | p.Move(pos.Sub(p.Position()), 0, 0)
33 | }
34 | }
35 |
36 | //go:linkname player_session github.com/df-mc/dragonfly/server/player.(*Player).session
37 | //noinspection ALL
38 | func player_session(*player.Player) *session.Session
39 |
40 | //go:linkname session_writePacket github.com/df-mc/dragonfly/server/session.(*Session).writePacket
41 | //noinspection ALL
42 | func session_writePacket(*session.Session, packet.Packet)
43 |
--------------------------------------------------------------------------------
/vasar/potion_handler.go:
--------------------------------------------------------------------------------
1 | package vasar
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/event"
5 | "github.com/df-mc/dragonfly/server/player"
6 | "github.com/vasar-network/practice/vasar/user"
7 | "image/color"
8 | )
9 |
10 | // PotionHandler ...
11 | type PotionHandler struct{}
12 |
13 | // HandleSplash ...
14 | func (PotionHandler) HandleSplash(ctx *event.Context, owner, splashed *player.Player) {
15 | if splashed == owner {
16 | // Don't waste time.
17 | return
18 | }
19 | if s, ok := user.Lookup(splashed); ok && s.Tagged() && s.Attacker() != owner {
20 | ctx.Cancel()
21 | }
22 | }
23 |
24 | // HandleParticle ...
25 | func (PotionHandler) HandleParticle(ctx *event.Context, owner *player.Player) {
26 | if s, ok := user.Lookup(owner); ok && !s.Settings().Visual.Splashes {
27 | ctx.Cancel()
28 | }
29 | }
30 |
31 | // HandleParticleColour ...
32 | func (PotionHandler) HandleParticleColour(ctx *event.Context, owner *player.Player, c *color.RGBA) {
33 | if u, ok := user.Lookup(owner); ok {
34 | switch u.Settings().Advanced.PotionSplashColor {
35 | case "invisible":
36 | ctx.Cancel()
37 | case "red":
38 | *c = color.RGBA{R: 255, A: 255}
39 | case "orange":
40 | *c = color.RGBA{R: 255, G: 155, A: 255}
41 | case "yellow":
42 | *c = color.RGBA{R: 255, G: 255, A: 255}
43 | case "green":
44 | *c = color.RGBA{G: 255, A: 255}
45 | case "aqua":
46 | *c = color.RGBA{G: 255, B: 255, A: 255}
47 | case "blue":
48 | *c = color.RGBA{R: 70, B: 255, A: 255}
49 | case "pink":
50 | *c = color.RGBA{R: 255, B: 255, A: 255}
51 | case "white":
52 | *c = color.RGBA{R: 255, G: 255, B: 255, A: 255}
53 | case "gray":
54 | *c = color.RGBA{R: 155, G: 155, B: 155, A: 255}
55 | case "black":
56 | *c = color.RGBA{R: 0, G: 0, B: 0, A: 255}
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/vasar/provider.go:
--------------------------------------------------------------------------------
1 | package vasar
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server"
5 | "github.com/df-mc/dragonfly/server/player"
6 | "github.com/df-mc/dragonfly/server/world"
7 | "github.com/google/uuid"
8 | )
9 |
10 | // Provider is a dummy player data provider to change the direction the player is looking towards.
11 | type Provider struct {
12 | srv *server.Server
13 | player.NopProvider
14 | }
15 |
16 | // Load ...
17 | func (p *Provider) Load(uuid.UUID) (player.Data, error) {
18 | return player.Data{
19 | Position: p.srv.World().Spawn().Vec3Middle(),
20 | GameMode: world.GameModeSurvival,
21 | Yaw: 180,
22 | Health: 20,
23 | MaxHealth: 20,
24 | Hunger: 20,
25 | SaturationLevel: 5,
26 | }, nil
27 | }
28 |
--------------------------------------------------------------------------------
/vasar/skins.go:
--------------------------------------------------------------------------------
1 | package vasar
2 |
3 | import (
4 | _ "embed"
5 | "encoding/json"
6 | "fmt"
7 | "github.com/df-mc/dragonfly/server/player/skin"
8 | "image"
9 | "image/draw"
10 | "image/png"
11 | "os"
12 | )
13 |
14 | var (
15 | // steve is the Vasar steve skin, used when the player has an unsupported skin.
16 | steve skin.Skin
17 | // bounds contains all possible bounds for skins.
18 | bounds [][][2][2]int
19 | )
20 |
21 | // init initializes the bounds for all possible skins.
22 | func init() {
23 | skinBounds, err := os.ReadFile("assets/skins/bounds.json")
24 | if err != nil {
25 | panic(err)
26 | }
27 | geometry, err := os.ReadFile("assets/skins/geometry.json")
28 | if err != nil {
29 | panic(err)
30 | }
31 | err = json.Unmarshal(skinBounds, &bounds)
32 | if err != nil {
33 | panic(err)
34 | }
35 |
36 | f, err := os.OpenFile("assets/skins/steve.png", os.O_RDONLY, 0644)
37 | if err != nil {
38 | panic(err)
39 | }
40 | img, err := png.Decode(f)
41 | if err != nil {
42 | panic(err)
43 | }
44 | _ = f.Close()
45 |
46 | rect := img.Bounds()
47 | rgba := image.NewRGBA(rect)
48 | draw.Draw(rgba, rect, img, rect.Min, draw.Src)
49 |
50 | steve = skin.New(rect.Dx(), rect.Dy())
51 | steve.ModelConfig.Default = "geometry.humanoid.custom"
52 | steve.Model = geometry
53 | steve.Pix = rgba.Pix
54 | }
55 |
56 | // searchTransparency searches for transparency in the given skin, returning the found percentage. This percentage is
57 | // between 0 and 1.
58 | func searchTransparency(skin skin.Skin) (float64, error) {
59 | max := skin.Bounds().Size()
60 | sizeBounds, err := sizeSpecificBounds(max)
61 | if err != nil {
62 | return 0.0, err
63 | }
64 |
65 | var transparent int
66 | for _, bound := range sizeBounds {
67 | if bound[1][0] > max.X || bound[1][1] > max.Y {
68 | // Skip bounds that are, well, out of bounds.
69 | continue
70 | }
71 | for y := bound[0][1]; y <= bound[1][1]; y++ {
72 | for x := bound[0][0]; x <= bound[1][0]; x++ {
73 | if skin.Pix[((max.X*y)+x)*4+3] < 127 {
74 | transparent++
75 | }
76 | }
77 | }
78 | }
79 | return float64(transparent) / float64(max.X*max.Y), nil
80 | }
81 |
82 | // sizeSpecificBounds returns the size specific bounds for the given size.
83 | func sizeSpecificBounds(size image.Point) ([][2][2]int, error) {
84 | switch size {
85 | case image.Point{X: 64, Y: 32}, image.Point{X: 64, Y: 64}:
86 | return bounds[0], nil
87 | case image.Point{X: 128, Y: 128}:
88 | return bounds[1], nil
89 | }
90 | return nil, fmt.Errorf("skin: unsupported skin size (%v)", size)
91 | }
92 |
--------------------------------------------------------------------------------
/vasar/user/chat_type.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | // ChatType represents different chats users may access.
4 | type ChatType struct {
5 | chat int
6 | }
7 |
8 | // ChatTypeGlobal returns the global chat type.
9 | func ChatTypeGlobal() ChatType {
10 | return ChatType{chat: 1}
11 | }
12 |
13 | // ChatTypeStaff returns the staff chat type.
14 | func ChatTypeStaff() ChatType {
15 | return ChatType{chat: 2}
16 | }
17 |
18 | // ChatTypeParty returns the party chat type.
19 | func ChatTypeParty() ChatType {
20 | return ChatType{chat: 3}
21 | }
22 |
23 | // ChatTypes returns a list of all chat types.
24 | func ChatTypes() []ChatType {
25 | return []ChatType{
26 | ChatTypeGlobal(),
27 | ChatTypeStaff(),
28 | ChatTypeParty(),
29 | }
30 | }
31 |
32 | // String ...
33 | func (d ChatType) String() string {
34 | switch d.chat {
35 | case 1:
36 | return "Global"
37 | case 2:
38 | return "Staff"
39 | case 3:
40 | return "Party"
41 | }
42 | panic("should never happen")
43 | }
44 |
--------------------------------------------------------------------------------
/vasar/user/custom.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/session"
5 | "github.com/df-mc/dragonfly/server/world"
6 | "github.com/go-gl/mathgl/mgl64"
7 | "github.com/sandertv/gophertunnel/minecraft/protocol/packet"
8 | )
9 |
10 | // SendSound sends a built-in sound to the user with a custom position.
11 | func (u *User) SendSound(pos mgl64.Vec3, sound world.Sound) {
12 | u.s.ViewSound(pos, sound)
13 | }
14 |
15 | // SendCustomParticle sends a custom particle to the user, or it's viewers as well.
16 | func (u *User) SendCustomParticle(id, data int32, pos mgl64.Vec3, public bool) {
17 | pk := &packet.LevelEvent{
18 | EventType: packet.LevelEventParticleLegacyEvent | id,
19 | EventData: data,
20 | Position: vec64To32(pos),
21 | }
22 |
23 | viewers := []world.Viewer{u.s}
24 | if public {
25 | viewers = u.viewers()
26 | }
27 |
28 | for _, v := range viewers {
29 | if s, ok := v.(*session.Session); ok {
30 | session_writePacket(s, pk)
31 | }
32 | }
33 | }
34 |
35 | // SendCustomSound sends a custom sound to the user, or it's viewers as well.
36 | func (u *User) SendCustomSound(sound string, volume, pitch float64, public bool) {
37 | pos := u.Player().Position()
38 | pk := &packet.PlaySound{
39 | SoundName: sound,
40 | Position: vec64To32(pos),
41 | Volume: float32(volume),
42 | Pitch: float32(pitch),
43 | }
44 |
45 | viewers := []world.Viewer{u.s}
46 | if public {
47 | viewers = u.viewers()
48 | }
49 |
50 | for _, v := range viewers {
51 | if s, ok := v.(*session.Session); ok {
52 | session_writePacket(s, pk)
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/vasar/user/device_group.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | // DeviceGroup represents a device group that a user may be in.
4 | type DeviceGroup struct {
5 | device int
6 | }
7 |
8 | // DeviceGroupKeyboardMouse returns a device group that allows keyboard and mouse players.
9 | func DeviceGroupKeyboardMouse() DeviceGroup {
10 | return DeviceGroup{device: 1}
11 | }
12 |
13 | // DeviceGroupMobile returns a device group that allows mobile players.
14 | func DeviceGroupMobile() DeviceGroup {
15 | return DeviceGroup{device: 2}
16 | }
17 |
18 | // DeviceGroupController returns a device group that allows controller players.
19 | func DeviceGroupController() DeviceGroup {
20 | return DeviceGroup{device: 3}
21 | }
22 |
23 | // DeviceGroups returns a list of all device groups.
24 | func DeviceGroups() []DeviceGroup {
25 | return []DeviceGroup{
26 | DeviceGroupKeyboardMouse(),
27 | DeviceGroupMobile(),
28 | DeviceGroupController(),
29 | }
30 | }
31 |
32 | // String ...
33 | func (d DeviceGroup) String() string {
34 | switch d.device {
35 | case 1:
36 | return "Keyboard/Mouse"
37 | case 2:
38 | return "Touch"
39 | case 3:
40 | return "Controller"
41 | }
42 | panic("should never happen")
43 | }
44 |
45 | // Compare ...
46 | func (d DeviceGroup) Compare(o DeviceGroup) bool {
47 | return d.device == o.device
48 | }
49 |
--------------------------------------------------------------------------------
/vasar/user/elo_range.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | // EloRange represents an elo range that a user may match within.
8 | type EloRange struct {
9 | min, max int
10 | }
11 |
12 | // NewEloRange creates a new EloRange from a base elo.
13 | func NewEloRange(elo int) EloRange {
14 | return EloRange{min: elo, max: elo}
15 | }
16 |
17 | // Min ...
18 | func (p EloRange) Min() int {
19 | return p.min
20 | }
21 |
22 | // Max ...
23 | func (p EloRange) Max() int {
24 | return p.max
25 | }
26 |
27 | // String ...
28 | func (p EloRange) String() string {
29 | return fmt.Sprintf("%d - %d", p.min, p.max)
30 | }
31 |
32 | const (
33 | // maxElo is the maximum elo value allowed.
34 | maxElo = 5000
35 | // minElo is the minimum elo value allowed.
36 | minElo = 0
37 | )
38 |
39 | // Extend ...
40 | func (p EloRange) Extend(min, max int) EloRange {
41 | if p.max+max > maxElo {
42 | p.max = maxElo
43 | } else {
44 | p.max += max
45 | }
46 | if p.min-min < minElo {
47 | p.min = minElo
48 | } else {
49 | p.min -= min
50 | }
51 | return p
52 | }
53 |
54 | // Compare ...
55 | func (p EloRange) Compare(elo int) bool {
56 | return elo >= p.min && elo <= p.max
57 | }
58 |
--------------------------------------------------------------------------------
/vasar/user/experience.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "github.com/sandertv/gophertunnel/minecraft/protocol"
5 | "github.com/sandertv/gophertunnel/minecraft/protocol/packet"
6 | "math"
7 | )
8 |
9 | // ResetExperienceProgress resets the user's experience progress.
10 | func (u *User) ResetExperienceProgress() {
11 | u.SendExperienceProgress(0, 0)
12 | }
13 |
14 | // SendExperienceProgress sends the experience progress to the user.
15 | func (u *User) SendExperienceProgress(level int, progress float64) {
16 | session_writePacket(u.s, &packet.UpdateAttributes{
17 | EntityRuntimeID: 1,
18 | Attributes: []protocol.Attribute{
19 | {
20 | Name: "minecraft:player.level",
21 | Value: float32(level),
22 | Max: float32(math.MaxInt32),
23 | },
24 | {
25 | Name: "minecraft:player.experience",
26 | Value: float32(progress),
27 | Max: 1.0,
28 | },
29 | },
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/vasar/user/input_handler.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "fmt"
5 | "github.com/df-mc/dragonfly/server/session"
6 | "github.com/sandertv/gophertunnel/minecraft/protocol/packet"
7 | )
8 |
9 | // PlayerAuthInputHandler ...
10 | type PlayerAuthInputHandler struct {
11 | u *User
12 | }
13 |
14 | // Handle ...
15 | func (h PlayerAuthInputHandler) Handle(p packet.Packet, s *session.Session) error {
16 | pk := p.(*packet.PlayerAuthInput)
17 | set := h.u.Settings()
18 |
19 | var deviceGroup DeviceGroup
20 | switch pk.InputMode {
21 | case packet.InputModeMouse:
22 | deviceGroup = DeviceGroupKeyboardMouse()
23 | set.Matchmaking.MatchWithKeyboard = true
24 | case packet.InputModeTouch:
25 | deviceGroup = DeviceGroupMobile()
26 | set.Matchmaking.MatchWithMobile = true
27 | case packet.InputModeGamePad:
28 | deviceGroup = DeviceGroupController()
29 | set.Matchmaking.MatchWithController = true
30 | default:
31 | return fmt.Errorf("unexpected input mode %d for current device group: %v", pk.InputMode, h.u.DeviceGroup())
32 | }
33 |
34 | h.u.SetSettings(set)
35 | h.u.deviceGroup.Store(deviceGroup)
36 | return (session.PlayerAuthInputHandler{}).Handle(p, s)
37 | }
38 |
--------------------------------------------------------------------------------
/vasar/user/ping_range.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | // PingRange represents a ping range that a user may match within.
8 | type PingRange struct {
9 | unrestricted bool
10 | min, max int
11 | }
12 |
13 | // NewPingRange creates a new PingRange from a base ping.
14 | func NewPingRange(ping int) PingRange {
15 | return PingRange{min: ping, max: ping}
16 | }
17 |
18 | // PingRangeUnrestricted returns the ping group with no restrictions.
19 | func PingRangeUnrestricted() PingRange {
20 | return PingRange{unrestricted: true}
21 | }
22 |
23 | // PingRangeTwentyFive returns the ping group of twenty-five or below.
24 | func PingRangeTwentyFive() PingRange {
25 | return PingRange{max: 25}
26 | }
27 |
28 | // PingRangeFifty returns the ping group of fifty or below.
29 | func PingRangeFifty() PingRange {
30 | return PingRange{min: 25, max: 50}
31 | }
32 |
33 | // PingRangeSeventyFive returns the ping group of seventy-five or below.
34 | func PingRangeSeventyFive() PingRange {
35 | return PingRange{min: 50, max: 75}
36 | }
37 |
38 | // PingRangeHundred returns the ping group of a hundred or below.
39 | func PingRangeHundred() PingRange {
40 | return PingRange{min: 75, max: 100}
41 | }
42 |
43 | // PingRangeHundredTwentyFive returns the ping group of a hundred and twenty-five or below.
44 | func PingRangeHundredTwentyFive() PingRange {
45 | return PingRange{min: 100, max: 125}
46 | }
47 |
48 | // PingRangeHundredFifty returns the ping group of a hundred and fifty or below.
49 | func PingRangeHundredFifty() PingRange {
50 | return PingRange{min: 125, max: 150}
51 | }
52 |
53 | // PingRanges returns all possible ping groups.
54 | func PingRanges() []PingRange {
55 | return []PingRange{
56 | PingRangeUnrestricted(),
57 | PingRangeTwentyFive(),
58 | PingRangeFifty(),
59 | PingRangeSeventyFive(),
60 | PingRangeHundred(),
61 | PingRangeHundredTwentyFive(),
62 | PingRangeHundredFifty(),
63 | }
64 | }
65 |
66 | // Unrestricted ...
67 | func (p PingRange) Unrestricted() bool {
68 | return p.unrestricted
69 | }
70 |
71 | // Min ...
72 | func (p PingRange) Min() int {
73 | return p.min
74 | }
75 |
76 | // Max ...
77 | func (p PingRange) Max() int {
78 | return p.max
79 | }
80 |
81 | // String ...
82 | func (p PingRange) String() string {
83 | if p.unrestricted {
84 | return "Unrestricted"
85 | }
86 | return fmt.Sprintf("%d - %d", p.min, p.max)
87 | }
88 |
89 | const (
90 | // maxPing is the maximum ping value allowed.
91 | maxPing = 5000
92 | // minPing is the minimum ping value allowed.
93 | minPing = 0
94 | )
95 |
96 | // Extend ...
97 | func (p PingRange) Extend(min, max int) PingRange {
98 | if p.unrestricted {
99 | return p
100 | }
101 | if p.max+max > maxPing {
102 | p.max = maxPing
103 | } else {
104 | p.max += max
105 | }
106 | if p.min-min < minPing {
107 | p.min = minPing
108 | } else {
109 | p.min -= min
110 | }
111 | return p
112 | }
113 |
114 | // Compare ...
115 | func (p PingRange) Compare(ping int) bool {
116 | if p.unrestricted {
117 | // Unrestricted, so any range is fine.
118 | return true
119 | }
120 | return ping > p.min || ping < p.max
121 | }
122 |
--------------------------------------------------------------------------------
/vasar/user/punishment.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import "time"
4 |
5 | // Punishment stores the punishments of a user.
6 | type Punishment struct {
7 | // Staff is the staff member who issued the punishment.
8 | Staff string `bson:"staff"`
9 | // Reason is the reason for the punishment.
10 | Reason string `bson:"reason"`
11 | // Occurrence is the time the punishment was issued.
12 | Occurrence time.Time `bson:"occurrence"`
13 | // Permanent is true if the punishment doesn't expire.
14 | Permanent bool `bson:"permanent"`
15 | // Expiration is the expiration time of the punishment.
16 | Expiration time.Time `bson:"expiration"`
17 | }
18 |
19 | // Remaining returns the remaining duration of the punishment.
20 | func (p Punishment) Remaining() time.Duration {
21 | return time.Until(p.Expiration).Round(time.Second)
22 | }
23 |
24 | // Expired checks if the punishment has expired.
25 | func (p Punishment) Expired() bool {
26 | return !p.Permanent && time.Now().After(p.Expiration)
27 | }
28 |
--------------------------------------------------------------------------------
/vasar/user/role.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "github.com/vasar-network/vails"
5 | "github.com/vasar-network/vails/role"
6 | "golang.org/x/exp/slices"
7 | "sort"
8 | "sync"
9 | "time"
10 | )
11 |
12 | // Roles is a user-based role manager for both offline users and online users.
13 | type Roles struct {
14 | roleMu sync.Mutex
15 | roles []vails.Role
16 | roleExpirations map[vails.Role]time.Time
17 | }
18 |
19 | // NewRoles creates a new Roles instance.
20 | func NewRoles(roles []vails.Role, expirations map[vails.Role]time.Time) *Roles {
21 | return &Roles{
22 | roles: roles,
23 | roleExpirations: expirations,
24 | }
25 | }
26 |
27 | // Add adds a role to the manager's role list.
28 | func (r *Roles) Add(ro vails.Role) {
29 | r.roleMu.Lock()
30 | r.roles = append(r.roles, ro)
31 | r.roleMu.Unlock()
32 | r.sortRoles()
33 | }
34 |
35 | // Remove removes a role from the manager's role list. Users are responsible for updating the highest role usages if
36 | // changed.
37 | func (r *Roles) Remove(ro vails.Role) bool {
38 | if _, ok := ro.(role.Default); ok {
39 | // You can't remove the default role.
40 | return false
41 | }
42 |
43 | r.roleMu.Lock()
44 | i := slices.IndexFunc(r.roles, func(other vails.Role) bool {
45 | return ro == other
46 | })
47 | r.roles = slices.Delete(r.roles, i, i+1)
48 | delete(r.roleExpirations, ro)
49 | r.roleMu.Unlock()
50 | r.sortRoles()
51 | return true
52 | }
53 |
54 | // Staff returns true if the roles contains a staff role.
55 | func (r *Roles) Staff() bool {
56 | return r.Contains(role.Trial{}, role.Operator{})
57 | }
58 |
59 | // Contains returns true if the manager has any of the given roles. Users are responsible for updating the highest role
60 | // usages if changed.
61 | func (r *Roles) Contains(roles ...vails.Role) bool {
62 | r.roleMu.Lock()
63 | defer r.roleMu.Unlock()
64 |
65 | var actualRoles []vails.Role
66 | for _, ro := range r.roles {
67 | r.propagateRoles(&actualRoles, ro)
68 | }
69 |
70 | for _, r := range roles {
71 | if i := slices.IndexFunc(actualRoles, func(other vails.Role) bool {
72 | return r == other
73 | }); i >= 0 {
74 | return true
75 | }
76 | }
77 | return false
78 | }
79 |
80 | // Expiration returns the expiration time for a role. If the role does not expire, the second return value will be false.
81 | func (r *Roles) Expiration(ro vails.Role) (time.Time, bool) {
82 | r.roleMu.Lock()
83 | defer r.roleMu.Unlock()
84 | e, ok := r.roleExpirations[ro]
85 | return e, ok
86 | }
87 |
88 | // Expire sets the expiration time for a role. If the role does not expire, the second return value will be false.
89 | func (r *Roles) Expire(ro vails.Role, t time.Time) {
90 | r.roleMu.Lock()
91 | defer r.roleMu.Unlock()
92 | r.roleExpirations[ro] = t
93 | }
94 |
95 | // Highest returns the highest role the manager has, in terms of hierarchy.
96 | func (r *Roles) Highest() vails.Role {
97 | r.roleMu.Lock()
98 | defer r.roleMu.Unlock()
99 | return r.roles[len(r.roles)-1]
100 | }
101 |
102 | // All returns the user's roles.
103 | func (r *Roles) All() []vails.Role {
104 | r.roleMu.Lock()
105 | defer r.roleMu.Unlock()
106 | return append(make([]vails.Role, 0, len(r.roles)), r.roles...)
107 | }
108 |
109 | // propagateRoles propagates roles to the user's role list.
110 | func (r *Roles) propagateRoles(actualRoles *[]vails.Role, role vails.Role) {
111 | *actualRoles = append(*actualRoles, role)
112 | if h, ok := role.(vails.HeirRole); ok {
113 | r.propagateRoles(actualRoles, h.Inherits())
114 | }
115 | }
116 |
117 | // sortRoles sorts the roles in the user's role list.
118 | func (r *Roles) sortRoles() {
119 | sort.SliceStable(r.roles, func(i, j int) bool {
120 | return role.Tier(r.roles[i]) < role.Tier(r.roles[j])
121 | })
122 | }
123 |
--------------------------------------------------------------------------------
/vasar/user/settings.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | // Settings contains all user configurable settings for Vasar.
4 | type Settings struct {
5 | // Display is a section of settings related to what the user interface should look like.
6 | Display struct {
7 | // CPS is true if the CPS counter should be enabled.
8 | CPS bool `bson:"cps"`
9 | // Scoreboard is true if the scoreboard should be enabled.
10 | Scoreboard bool `bson:"scoreboard"`
11 | } `bson:"display"`
12 | // Visual is a section of settings related to visual features, such as lightning or potion splashes.
13 | Visual struct {
14 | // Lightning is true if lightning deaths should be enabled.
15 | Lightning bool `bson:"lightning"`
16 | // Splashes is true if potion splashes should be enabled.
17 | Splashes bool `bson:"splashes"`
18 | // PearlAnimation is true if players should appear to zoom instead of instantly teleport.
19 | PearlAnimation bool `bson:"pearl_animation"`
20 | } `bson:"visual"`
21 | // Gameplay is a section of settings related to gameplay features, such as the pearl animation or instant respawn.
22 | Gameplay struct {
23 | // ToggleSprint is true if the user should automatically toggle sprinting.
24 | ToggleSprint bool `bson:"toggle_sprint"`
25 | // AutoReapplyKit is true if the user should automatically reapply the kit.
26 | AutoReapplyKit bool `bson:"auto_reapply_kit"`
27 | // PreventInterference is true if the user should prevent interference with other players.
28 | PreventInterference bool `bson:"prevent_interference"`
29 | // PreventClutter is true if clutter should be prevented.
30 | PreventClutter bool `bson:"prevent_clutter"`
31 | // InstantRespawn is true if the user should respawn instantly.
32 | InstantRespawn bool `bson:"instant_respawn"`
33 | } `bson:"gameplay"`
34 | // Privacy is a section of settings related to privacy features, such as duel requests or PMs.
35 | Privacy struct {
36 | // PrivateMessages is true if private messages should be allowed.
37 | PrivateMessages bool `bson:"private_messages"`
38 | // PublicStatistics is true if the user's statistics should be public.
39 | PublicStatistics bool `bson:"public_statistics"`
40 | // DuelRequests is true if duel requests should be allowed.
41 | DuelRequests bool `bson:"duel_requests"`
42 | } `bson:"privacy"`
43 | // Matchmaking is a section of settings related to matchmaking features, such as the accepted ping range.
44 | Matchmaking struct {
45 | // MatchWithMobile is true if the user should be matched with mobile players.
46 | MatchWithMobile bool `bson:"match_with_mobile"`
47 | // MatchWithController is true if the user should be matched with controller players.
48 | MatchWithController bool `bson:"match_with_controller"`
49 | // MatchWithKeyboard is true if the user should be matched with keyboard players.
50 | MatchWithKeyboard bool `bson:"match_with_keyboard"`
51 | // PingRange is the maximum ping range opponents of the user can have.
52 | PingRange uint8 `bson:"ping_range"`
53 | } `bson:"matchmaking"`
54 | // Advanced is a section of settings related to advanced features, such as capes or splash colours.
55 | Advanced struct {
56 | // Cape is the name of the user's cape.
57 | Cape string `bson:"cape"`
58 | // ParticleMultiplier is the multiplier of combat particles.
59 | ParticleMultiplier int `bson:"particle_multiplier"`
60 | // PotionSplashColor is the colour of the potion splash particles.
61 | PotionSplashColor string `bson:"potion_splash_colour"`
62 | // VasarPlusColour is the colour of the user's Vasar Plus role.
63 | VasarPlusColour string `bson:"vasar_plus_colour"`
64 | } `bson:"advanced"`
65 | }
66 |
67 | // DefaultSettings returns the default settings for a new user.
68 | func DefaultSettings() Settings {
69 | s := Settings{}
70 | s.Display.CPS = true
71 | s.Display.Scoreboard = true
72 |
73 | s.Visual.Lightning = true
74 | s.Visual.Splashes = true
75 |
76 | s.Gameplay.AutoReapplyKit = true
77 |
78 | s.Privacy.PrivateMessages = true
79 | s.Privacy.PublicStatistics = true
80 | s.Privacy.DuelRequests = true
81 |
82 | s.Matchmaking.MatchWithMobile = true
83 | s.Matchmaking.MatchWithController = true
84 | s.Matchmaking.MatchWithKeyboard = true
85 |
86 | s.Advanced.VasarPlusColour = "§0"
87 | return s
88 | }
89 |
--------------------------------------------------------------------------------
/vasar/user/stats.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "github.com/vasar-network/practice/vasar/game"
5 | )
6 |
7 | // Stats contains all the stats of a user.
8 | type Stats struct {
9 | // Elo is the amount of elo that the user has altogether.
10 | Elo int32 `bson:"elo"`
11 | // GameElo maps between a game and the amount of elo that the user has in that game.
12 | GameElo map[string]int32 `bson:"game_elo"`
13 |
14 | // Kills is the amount of players the user has killed.
15 | Kills uint32 `bson:"kills"`
16 | // Deaths is the amount of times the user has died.
17 | Deaths uint32 `bson:"deaths"`
18 | // RankedWins is the amount of ranked wins the user has.
19 | UnrankedWins uint32 `bson:"unranked_wins"`
20 | // RankedLosses is the amount of unranked losses the user has.
21 | UnrankedLosses uint32 `bson:"unranked_losses"`
22 | // RankedWins is the amount of ranked wins the user has.
23 | RankedWins uint32 `bson:"ranked_wins"`
24 | // RankedLosses is the amount of ranked losses the user has.
25 | RankedLosses uint32 `bson:"ranked_losses"`
26 |
27 | // KillStreak is the current streak of kills the user has without dying.
28 | KillStreak uint32 `bson:"kill_streak"`
29 | // BestKillStreak is the highest kill-streak the user has ever gotten.
30 | BestKillStreak uint32 `bson:"best_kill_streak"`
31 | }
32 |
33 | // defaultElo is the default elo all players start with.
34 | const defaultElo = 1000
35 |
36 | // DefaultStats returns the default stats of a user.
37 | func DefaultStats() Stats {
38 | s := Stats{
39 | Elo: defaultElo,
40 | GameElo: make(map[string]int32, len(game.Games())),
41 | }
42 | for _, g := range game.Games() {
43 | s.GameElo[g.String()] = defaultElo
44 | }
45 | return s
46 | }
47 |
--------------------------------------------------------------------------------
/vasar/user/tag.go:
--------------------------------------------------------------------------------
1 | package user
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/player"
5 | "github.com/df-mc/dragonfly/server/player/title"
6 | "github.com/vasar-network/vails/lang"
7 | "time"
8 | )
9 |
10 | // Tagged checks if the player is in combat.
11 | func (u *User) Tagged() bool {
12 | return time.Now().Before(u.TagExpiration())
13 | }
14 |
15 | // TagExpiration returns the expiration time of the combat time.
16 | func (u *User) TagExpiration() time.Time {
17 | u.tagMu.Lock()
18 | defer u.tagMu.Unlock()
19 | return u.tagExpiration
20 | }
21 |
22 | // Attacker returns the last attacker of the player.
23 | func (u *User) Attacker() *player.Player {
24 | u.tagMu.Lock()
25 | defer u.tagMu.Unlock()
26 | return u.attacker
27 | }
28 |
29 | // Tag starts the combat tag and notifies the player if specified.
30 | func (u *User) Tag(attacker *player.Player, kill, notify bool) {
31 | u.tagMu.Lock()
32 | defer u.tagMu.Unlock()
33 |
34 | now := time.Now()
35 | seconds := time.Second * 20
36 | if kill && u.tagExpiration.Sub(now) > time.Second*5 {
37 | seconds /= 5
38 | if notify {
39 | u.p.SendTitle(title.New().WithActionText(lang.Translatef(u.p.Locale(), "combat.tag.reduced")))
40 | }
41 | }
42 | if attacker != nil {
43 | u.attacker = attacker
44 | }
45 |
46 | if !now.Before(u.tagExpiration) {
47 | if notify {
48 | u.p.SendTitle(title.New().WithActionText(lang.Translatef(u.p.Locale(), "combat.tag.added")))
49 | }
50 |
51 | go func() {
52 | t := time.NewTicker(time.Second)
53 | defer t.Stop()
54 | for {
55 | select {
56 | case <-u.tagC:
57 | return
58 | case <-t.C:
59 | if !u.Tagged() {
60 | u.tagMu.Lock()
61 | if notify {
62 | u.p.SendTitle(title.New().WithActionText(lang.Translatef(u.p.Locale(), "combat.tag.expired")))
63 | }
64 | u.attacker = nil
65 | u.tagMu.Unlock()
66 | return
67 | }
68 | }
69 | }
70 | }()
71 | }
72 |
73 | u.tagExpiration = now.Add(seconds)
74 | }
75 |
76 | // RemoveTag removes the existing combat tag without notifying the user.
77 | func (u *User) RemoveTag() {
78 | u.tagMu.Lock()
79 | u.tagC <- struct{}{}
80 | u.tagExpiration = time.Time{}
81 | u.attacker = nil
82 | u.tagMu.Unlock()
83 | }
84 |
--------------------------------------------------------------------------------
/vasar/world_handler.go:
--------------------------------------------------------------------------------
1 | package vasar
2 |
3 | import (
4 | "github.com/df-mc/dragonfly/server/block/cube"
5 | "github.com/df-mc/dragonfly/server/event"
6 | "github.com/df-mc/dragonfly/server/world"
7 | "github.com/df-mc/dragonfly/server/world/sound"
8 | "github.com/go-gl/mathgl/mgl64"
9 | "github.com/vasar-network/practice/vasar/entity"
10 | )
11 |
12 | // WorldHandler ...
13 | type WorldHandler struct {
14 | world.NopHandler
15 | }
16 |
17 | // HandleEntitySpawn ...
18 | func (WorldHandler) HandleEntitySpawn(e world.Entity) {
19 | if v, ok := e.(*entity.VasarPearl); ok {
20 | v.Handle(PearlHandler{})
21 | } else if p, ok := e.(*entity.SplashPotion); ok {
22 | p.Handle(PotionHandler{})
23 | }
24 | }
25 |
26 | // HandleSound ...
27 | func (WorldHandler) HandleSound(ctx *event.Context, s world.Sound, _ mgl64.Vec3) {
28 | if _, ok := s.(sound.Attack); ok {
29 | ctx.Cancel()
30 | }
31 | }
32 |
33 | // HandleLiquidHarden ...
34 | func (WorldHandler) HandleLiquidHarden(ctx *event.Context, _ cube.Pos, _, _, _ world.Block) {
35 | ctx.Cancel()
36 | }
37 |
--------------------------------------------------------------------------------