├── .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 | --------------------------------------------------------------------------------