226 lines
6.9 KiB
Go
226 lines
6.9 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/disgoorg/disgo/discord"
|
|
"github.com/disgoorg/disgo/handler"
|
|
|
|
"git.sunturtle.xyz/zephyr/horse/cmd/horsebot/autocomplete"
|
|
"git.sunturtle.xyz/zephyr/horse/horse"
|
|
)
|
|
|
|
type skillServer struct {
|
|
skills map[horse.SkillID]horse.Skill
|
|
byName map[string]horse.SkillID
|
|
groups map[horse.SkillGroupID]horse.SkillGroup
|
|
autocom autocomplete.Set[discord.AutocompleteChoice]
|
|
}
|
|
|
|
func newSkillServer(skills []horse.Skill, groups []horse.SkillGroup) *skillServer {
|
|
s := skillServer{
|
|
skills: make(map[horse.SkillID]horse.Skill, len(skills)),
|
|
byName: make(map[string]horse.SkillID, len(skills)),
|
|
groups: make(map[horse.SkillGroupID]horse.SkillGroup, len(groups)),
|
|
}
|
|
for _, skill := range skills {
|
|
s.skills[skill.ID] = skill
|
|
s.byName[skill.Name] = skill.ID
|
|
switch {
|
|
case skill.UniqueOwner == "":
|
|
s.autocom.Add(skill.Name, discord.AutocompleteChoiceString{Name: skill.Name, Value: strconv.Itoa(int(skill.ID))})
|
|
case skill.Rarity >= 3:
|
|
s.autocom.Add(skill.Name, discord.AutocompleteChoiceString{Name: skill.Name, Value: skill.Name})
|
|
s.autocom.Add(skill.UniqueOwner, discord.AutocompleteChoiceString{Name: "Unique: " + skill.UniqueOwner, Value: strconv.Itoa(int(skill.ID))})
|
|
default:
|
|
s.autocom.Add(skill.Name, discord.AutocompleteChoiceString{Name: skill.Name + " (Inherited)", Value: strconv.Itoa(int(skill.ID))})
|
|
s.autocom.Add(skill.UniqueOwner, discord.AutocompleteChoiceString{Name: "Inherited unique: " + skill.UniqueOwner, Value: skill.Name})
|
|
}
|
|
}
|
|
for _, g := range groups {
|
|
s.groups[g.ID] = g
|
|
}
|
|
return &s
|
|
}
|
|
|
|
func (s *skillServer) slash(data discord.SlashCommandInteractionData, e *handler.CommandEvent) error {
|
|
q := data.String("query")
|
|
id, err := strconv.ParseInt(q, 10, 32)
|
|
if err == nil {
|
|
// note inverted condition; this is when we have an id
|
|
id = int64(s.skills[horse.SkillID(id)].ID)
|
|
}
|
|
if id == 0 {
|
|
// Either we weren't given a number or the number doesn't match any skill ID.
|
|
v := s.byName[q]
|
|
if v == 0 {
|
|
// No such skill.
|
|
m := discord.MessageCreate{
|
|
Content: "No such skill.",
|
|
Flags: discord.MessageFlagEphemeral,
|
|
}
|
|
return e.CreateMessage(m)
|
|
}
|
|
id = int64(v)
|
|
}
|
|
m := discord.MessageCreate{
|
|
Components: []discord.LayoutComponent{s.render(horse.SkillID(id))},
|
|
Flags: discord.MessageFlagIsComponentsV2,
|
|
}
|
|
if !data.Bool("share") {
|
|
m.Flags |= discord.MessageFlagEphemeral
|
|
}
|
|
return e.CreateMessage(m)
|
|
}
|
|
|
|
func (s *skillServer) autocomplete(e *handler.AutocompleteEvent) error {
|
|
q := e.Data.String("query")
|
|
opts := s.autocom.Find(nil, q)
|
|
return e.AutocompleteResult(opts[:min(len(opts), 25)])
|
|
}
|
|
|
|
func (s *skillServer) button(data discord.ButtonInteractionData, e *handler.ComponentEvent) error {
|
|
id, err := strconv.ParseInt(e.Vars["id"], 10, 32)
|
|
if err != nil {
|
|
m := discord.MessageCreate{
|
|
Content: "That button produced an invalid skill ID. That's not supposed to happen.",
|
|
Flags: discord.MessageFlagEphemeral,
|
|
}
|
|
return e.CreateMessage(m)
|
|
}
|
|
m := discord.MessageUpdate{
|
|
Components: &[]discord.LayoutComponent{s.render(horse.SkillID(id))},
|
|
}
|
|
return e.UpdateMessage(m)
|
|
}
|
|
|
|
func (s *skillServer) render(id horse.SkillID) discord.ContainerComponent {
|
|
skill, ok := s.skills[id]
|
|
if !ok {
|
|
slog.Error("invalid skill id", slog.Int("id", int(id)))
|
|
return discord.NewContainer(discord.NewTextDisplayf("invalid skill ID %v made it to render", id))
|
|
}
|
|
|
|
thumburl := fmt.Sprintf("https://gametora.com/images/umamusume/skill_icons/utx_ico_skill_%d.png", skill.IconID)
|
|
top := "## " + skill.Name
|
|
if skill.UniqueOwner != "" {
|
|
top += "\n-# " + skill.UniqueOwner
|
|
}
|
|
r := discord.NewContainer(
|
|
discord.NewSection(
|
|
discord.NewTextDisplay(top),
|
|
discord.NewTextDisplay(skill.Description),
|
|
).WithAccessory(discord.NewThumbnail(thumburl)),
|
|
)
|
|
var skilltype string
|
|
switch {
|
|
case skill.Rarity == 3, skill.Rarity == 4, skill.Rarity == 5:
|
|
// unique of various star levels
|
|
r.AccentColor = 0xaca4d4
|
|
skilltype = "Unique Skill"
|
|
case skill.UniqueOwner != "":
|
|
r.AccentColor = 0xcccccc
|
|
skilltype = "Inherited Unique"
|
|
case skill.Rarity == 2:
|
|
// rare (gold)
|
|
r.AccentColor = 0xd7c25b
|
|
skilltype = "Rare Skill"
|
|
case skill.GroupRate == -1:
|
|
// negative (purple) skill
|
|
r.AccentColor = 0x9151d4
|
|
skilltype = "Negative Skill"
|
|
case !skill.WitCheck:
|
|
// should be passive (green)
|
|
r.AccentColor = 0x66ae1c
|
|
skilltype = "Passive Skill"
|
|
case isDebuff(skill):
|
|
// debuff (red)
|
|
r.AccentColor = 0xe34747
|
|
skilltype = "Debuff Skill"
|
|
case skill.Rarity == 1:
|
|
// common (white)
|
|
r.AccentColor = 0xcccccc
|
|
skilltype = "Common Skill"
|
|
}
|
|
r.Components = append(r.Components, discord.NewSmallSeparator())
|
|
text := make([]string, 0, 3)
|
|
abils := make([]string, 0, 3)
|
|
for _, act := range skill.Activations {
|
|
text, abils = text[:0], abils[:0]
|
|
if act.Precondition != "" {
|
|
text = append(text, "Precondition: "+formatCondition(act.Precondition))
|
|
}
|
|
text = append(text, "Condition: "+formatCondition(act.Condition))
|
|
var t string
|
|
switch {
|
|
case act.Duration < 0:
|
|
// passive; do nothing
|
|
case act.Duration == 0:
|
|
t = "Instantaneous "
|
|
case act.Duration >= 500e4:
|
|
t = "Permanent "
|
|
case act.DurScale == horse.DurationDirect:
|
|
t = "For " + act.Duration.String() + "s, "
|
|
default:
|
|
t = "For " + act.Duration.String() + "s " + act.DurScale.String() + ", "
|
|
}
|
|
for _, a := range act.Abilities {
|
|
abils = append(abils, a.String())
|
|
}
|
|
t += strings.Join(abils, ", ")
|
|
if act.Cooldown > 0 && act.Cooldown < 500e4 {
|
|
t += " on " + act.Cooldown.String() + "s cooldown"
|
|
}
|
|
text = append(text, t)
|
|
r.Components = append(r.Components, discord.NewTextDisplay(strings.Join(text, "\n")))
|
|
}
|
|
|
|
l := discord.NewTextDisplayf("%s ・ SP cost %d ・ Grade value %d ・ [Conditions on GameTora](https://gametora.com/umamusume/skill-condition-viewer?skill=%d)", skilltype, skill.SPCost, skill.GradeValue, skill.ID)
|
|
r.Components = append(r.Components, discord.NewSmallSeparator(), l)
|
|
rel := make([]horse.Skill, 0, 4)
|
|
group := s.groups[skill.Group]
|
|
for _, id := range [...]horse.SkillID{group.Skill1, group.Skill2, group.Skill3, group.SkillBad} {
|
|
if id != 0 {
|
|
rel = append(rel, s.skills[id])
|
|
}
|
|
}
|
|
if len(rel) > 1 {
|
|
buttons := make([]discord.InteractiveComponent, 0, 4)
|
|
for _, rs := range rel {
|
|
name := rs.Name
|
|
if u := s.skills[rs.ID]; u.UniqueOwner != "" && u.Rarity == 1 {
|
|
name += " (Inherited)"
|
|
}
|
|
b := discord.NewSecondaryButton(name, fmt.Sprintf("/skill/swap/%d", rs.ID))
|
|
if rs.ID == id {
|
|
b = b.AsDisabled()
|
|
}
|
|
buttons = append(buttons, b)
|
|
}
|
|
r.Components = append(r.Components, discord.NewActionRow(buttons...))
|
|
}
|
|
return r
|
|
}
|
|
|
|
func formatCondition(s string) string {
|
|
s = strings.ReplaceAll(s, "&", " & ")
|
|
if strings.ContainsRune(s, '@') {
|
|
return "```\n" + strings.ReplaceAll(s, "@", "\n@\n") + "```"
|
|
}
|
|
return "`" + s + "`"
|
|
}
|
|
|
|
func isDebuff(s horse.Skill) bool {
|
|
for _, act := range s.Activations {
|
|
for _, a := range act.Abilities {
|
|
if a.Value < 0 {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|