diff --git a/main.go b/main.go index 8a1fdae..0afbc50 100644 --- a/main.go +++ b/main.go @@ -42,6 +42,7 @@ import ( "github.com/velour/catbase/plugins/tell" "github.com/velour/catbase/plugins/tldr" "github.com/velour/catbase/plugins/twitch" + "github.com/velour/catbase/plugins/velouremon" "github.com/velour/catbase/plugins/your" "github.com/velour/catbase/plugins/zork" ) @@ -124,6 +125,7 @@ func main() { b.AddPlugin(nerdepedia.New(b)) b.AddPlugin(tldr.New(b)) b.AddPlugin(stock.New(b)) + b.AddPlugin(velouremon.New(b)) b.AddPlugin(cli.New(b)) // catches anything left, will always return true b.AddPlugin(fact.New(b)) diff --git a/plugins/velouremon/ability.go b/plugins/velouremon/ability.go new file mode 100644 index 0000000..ddef887 --- /dev/null +++ b/plugins/velouremon/ability.go @@ -0,0 +1,25 @@ +package velouremon + +import ( + "fmt" +) + +type Ability struct { + ID int `db:"id"` + Name string `db:"name"` + Damage int `db:"damage"` + Heal int `db:"heal"` + Shield int `db:"shield"` + Weaken int `db:"weaken"` + Critical int `db:"critical"` +} + +type AbilityRef struct { + ID int `db:"id"` + Creature int `db:"creatureref"` + Ability int `db:"ability"` +} + +func (a *Ability) string() string { + return fmt.Sprintf("%s : %d DM, %d HL, %d SH, %d WK, %d CR\n", a.Name, a.Damage, a.Heal, a.Shield, a.Weaken, a.Critical) +} diff --git a/plugins/velouremon/ability_db.go b/plugins/velouremon/ability_db.go new file mode 100644 index 0000000..bc24e9f --- /dev/null +++ b/plugins/velouremon/ability_db.go @@ -0,0 +1,84 @@ +package velouremon + +import ( + "fmt" + + "github.com/rs/zerolog/log" +) + +func (vp *VelouremonPlugin) loadAbilities() error { + rows, err := vp.db.Queryx("select * from velouremon_abilities;") + if err != nil { + log.Error().Err(err) + return err + } + defer rows.Close() + + for rows.Next() { + ability := &Ability{} + err := rows.Scan(ability) + if err != nil { + log.Error().Err(err) + return err + } + vp.abilities = append(vp.abilities, ability) + } + return nil +} + +func (vp *VelouremonPlugin) loadAbilitiesForCreatureRef(ref *CreatureRef) ([]*Ability, error) { + abilityRefs, err := vp.loadAbilityRefsForCreature(ref) + if err != nil { + return nil, err + } + abilities, err := vp.loadAbilitiesFromRefs(abilityRefs) + if err != nil { + return nil, err + } + return abilities, nil +} + +func (vp *VelouremonPlugin) loadAbilityRefsForCreature(ref *CreatureRef) ([]*AbilityRef, error) { + rows, err := vp.db.Queryx(fmt.Sprintf("select * from velouremon_ability_ref where creatureref = %d;", ref.ID)) + if err != nil { + log.Error().Err(err) + return nil, err + } + defer rows.Close() + + abilities := []*AbilityRef{} + for rows.Next() { + ability := &AbilityRef{} + err := rows.Scan(ability) + + if err != nil { + log.Error().Err(err) + return nil, err + } + abilities = append(abilities, ability) + } + return abilities, nil +} + +func (vp *VelouremonPlugin) loadAbilitiesFromRefs(refs []*AbilityRef) ([]*Ability, error) { + abilities := []*Ability{} + for _, ref := range refs { + ability, err := vp.loadAbilityFromRef(ref) + if err != nil { + log.Error().Err(err) + return nil, err + } + abilities = append(abilities, ability) + } + return abilities, nil +} + +func (vp *VelouremonPlugin) loadAbilityFromRef(ref *AbilityRef) (*Ability, error) { + ability := &Ability{} + err := vp.db.QueryRowx(`select * from velouremon_abilities where id = ? LIMIT 1;`, ref.Ability).StructScan(ability) + if err != nil { + log.Error().Err(err) + return nil, err + } + return ability, nil +} diff --git a/plugins/velouremon/commands.go b/plugins/velouremon/commands.go new file mode 100644 index 0000000..5da5f57 --- /dev/null +++ b/plugins/velouremon/commands.go @@ -0,0 +1,10 @@ +package velouremon + +import ( + "github.com/velour/catbase/bot" +) + +func (vp *VelouremonPlugin) handleStatus(c bot.Connector, player *Player, tokens []string) bool { + vp.bot.Send(c, bot.Message, vp.channel, player.string()) + return true +} diff --git a/plugins/velouremon/creature.go b/plugins/velouremon/creature.go new file mode 100644 index 0000000..900e10b --- /dev/null +++ b/plugins/velouremon/creature.go @@ -0,0 +1,30 @@ +package velouremon + +import ( + "fmt" +) + +type Creature struct { + ID int `db:"id"` + Name string `db:"name"` + Health int + Experience int + Defense int `db:"defense"` + Attack int `db:"attack"` + Abilities []*Ability +} + +type CreatureRef struct { + ID int `db:"id"` + Creature int `db:"creature"` + Health int `db:"health"` + Experience int `db:"experience"` +} + +func (c *Creature) string() string { + message := fmt.Sprintf("%s : %d HP, %d XP, %d DEF, %d ATT\n", c.Name, c.Health, c.Experience, c.Defense, c.Attack) + for _, ability := range c.Abilities { + message += "\t" + ability.string() + } + return message +} diff --git a/plugins/velouremon/creature_db.go b/plugins/velouremon/creature_db.go new file mode 100644 index 0000000..8f06b74 --- /dev/null +++ b/plugins/velouremon/creature_db.go @@ -0,0 +1,116 @@ +package velouremon + +import ( + "fmt" + + "github.com/rs/zerolog/log" +) + +func (vp *VelouremonPlugin) loadCreatures() error { + rows, err := vp.db.Queryx("select * from velouremon_creatures;") + if err != nil { + log.Error().Err(err) + return err + } + defer rows.Close() + + for rows.Next() { + creature := &Creature{ + Health: 255, + Experience: 0, + } + err := rows.Scan(creature) + if err != nil { + log.Error().Err(err) + return err + } + vp.creatures = append(vp.creatures, creature) + } + return nil +} + +func (vp *VelouremonPlugin) loadCreaturesForPlayer(player *Player) ([]*Creature, error) { + creatureRefs, err := vp.loadCreatureRefsForPlayer(player) + if err != nil { + return nil, err + } + creatures, err := vp.loadCreaturesFromRefs(creatureRefs) + if err != nil { + return nil, err + } + return creatures, nil +} + +func (vp *VelouremonPlugin) loadCreatureRefsForPlayer(player *Player) ([]*CreatureRef, error) { + rows, err := vp.db.Queryx(fmt.Sprintf("select * from velouremon_creature_ref where player = %d;", player.ID)) + if err != nil { + log.Error().Err(err) + return nil, err + } + defer rows.Close() + + creatures := []*CreatureRef{} + for rows.Next() { + creature := &CreatureRef{} + err := rows.Scan(creature) + + if err != nil { + log.Error().Err(err) + return nil, err + } + creatures = append(creatures, creature) + } + return creatures, nil +} + +func (vp *VelouremonPlugin) loadCreaturesFromRefs(refs []*CreatureRef) ([]*Creature, error) { + creatures := []*Creature{} + for _, ref := range refs { + creature, err := vp.loadCreaturesFromRef(ref) + if err != nil { + log.Error().Err(err) + return nil, err + } + creatures = append(creatures, creature) + } + return creatures, nil +} + +func (vp *VelouremonPlugin) loadCreaturesFromRef(ref *CreatureRef) (*Creature, error) { + creature := &Creature{} + err := vp.db.QueryRowx(`select * from velouremon_creatures where id = ? LIMIT 1;`, ref.Creature).StructScan(creature) + if err != nil { + log.Error().Err(err) + return nil, err + } + + creature.Abilities, err = vp.loadAbilitiesForCreatureRef(ref) + if err != nil { + log.Error().Err(err) + return nil, err + } + + creature.Health = ref.Health + creature.Experience = ref.Experience + return creature, nil +} + +func (vp *VelouremonPlugin) savePlayerCreatures(player *Player) error { + for _, creature := range player.Creatures { + err := vp.saveCreatureForPlayer(player, creature) + if err != nil { + log.Error().Err(err) + return err + } + } + return nil +} + +func (vp *VelouremonPlugin) saveCreatureForPlayer(player *Player, creature *Creature) error { + _, err := vp.db.Exec(`update velouremon_creature_ref set health = ?, experience = ? where id = ?;`, creature.ID, creature.Health, creature.Experience) + if err != nil { + log.Error().Err(err) + return err + } + return nil +} diff --git a/plugins/velouremon/database.go b/plugins/velouremon/database.go new file mode 100644 index 0000000..ab7482e --- /dev/null +++ b/plugins/velouremon/database.go @@ -0,0 +1,62 @@ +package velouremon + +import ( + "github.com/rs/zerolog/log" +) + +func (vp *VelouremonPlugin) checkAndBuildDBOrFail() { + if _, err := vp.db.Exec(`create table if not exists velouremon_players ( + id integer primary key, + chatid string, + player string, + health integer, + experience integer + );`); err != nil { + log.Fatal().Err(err) + } + + if _, err := vp.db.Exec(`create table if not exists velouremon_creature_ref ( + id integer primary key, + player integer, + creature integer, + health integer, + experience integer + );`); err != nil { + log.Fatal().Err(err) + } + + if _, err := vp.db.Exec(`create table if not exists velouremon_creatures ( + id integer primary key, + creature string, + defense integer, + attack integer + );`); err != nil { + log.Fatal().Err(err) + } + + if _, err := vp.db.Exec(`create table if not exists velouremon_ability_ref ( + id integer primary key, + creatureref integer, + ability integer + );`); err != nil { + log.Fatal().Err(err) + } + + if _, err := vp.db.Exec(`create table if not exists velouremon_abilities ( + id integer primary key, + name string, + damage int, + heal int, + shield int, + weaken int, + critical int + );`); err != nil { + log.Fatal().Err(err) + } +} + +func (vp *VelouremonPlugin) loadFromDB() { + vp.loadPlayers() + vp.loadCreatures() + vp.loadAbilities() +} diff --git a/plugins/velouremon/interaction.go b/plugins/velouremon/interaction.go new file mode 100644 index 0000000..b3b519e --- /dev/null +++ b/plugins/velouremon/interaction.go @@ -0,0 +1,28 @@ +package velouremon + +import ( + "fmt" + "math/rand" + "time" + + "github.com/velour/catbase/bot" +) + +type Interaction struct { + players []*Player + creatures []*Creature +} + +func randomInteraction(c bot.Connector, vp *VelouremonPlugin) { + for { + <-vp.timer.C + if vp.channel != "" { + creature := vp.creatures[rand.Intn(len(vp.creatures))] + message := fmt.Sprintf("A wild %s appeared.", creature.Name) + vp.bot.Send(c, bot.Message, vp.channel, message) + } + + dur, _ := time.ParseDuration("1h") + vp.timer.Reset(dur) + } +} diff --git a/plugins/velouremon/player.go b/plugins/velouremon/player.go new file mode 100644 index 0000000..a5fe82c --- /dev/null +++ b/plugins/velouremon/player.go @@ -0,0 +1,36 @@ +package velouremon + +import ( + "fmt" + "strings" + + "github.com/velour/catbase/bot/user" +) + +type Player struct { + ID int64 `db:"id"` + ChatID string `db:"chatid"` + Name string `db:"name"` + Health int `db:"health"` + Experience int `db:"experience"` + Creatures []*Creature +} + +func (vp *VelouremonPlugin) getOrAddPlayer(u *user.User) (*Player, error) { + var player *Player + for _, p := range vp.players { + if p.ChatID == u.ID { + return player, nil + } + } + return vp.addPlayer(u) +} + +func (p *Player) string() string { + message := fmt.Sprintf("%s : %d HP, %d XP\n", p.Name, p.Health, p.Experience) + for _, creature := range p.Creatures { + message += "\t" + strings.ReplaceAll(creature.string(), "\n", "\n\t") + message = strings.TrimSuffix(message, "\t") + } + return message +} diff --git a/plugins/velouremon/player_db.go b/plugins/velouremon/player_db.go new file mode 100644 index 0000000..72dc3e8 --- /dev/null +++ b/plugins/velouremon/player_db.go @@ -0,0 +1,72 @@ +package velouremon + +import ( + "github.com/rs/zerolog/log" + + "github.com/velour/catbase/bot/user" +) + +func (vp *VelouremonPlugin) loadPlayers() error { + rows, err := vp.db.Queryx("select * from velouremon_players;") + if err != nil { + log.Error().Err(err) + return err + } + defer rows.Close() + + for rows.Next() { + player := &Player{} + err := rows.Scan(player) + if err != nil { + log.Error().Err(err) + return err + } + vp.players = append(vp.players, player) + player.Creatures, err = vp.loadCreaturesForPlayer(player) + if err != nil { + log.Error().Err(err) + return err + } + } + return nil +} + +func (vp *VelouremonPlugin) addPlayer(p *user.User) (*Player, error) { + player := &Player{ + ChatID: p.ID, + Name: p.Name, + Health: 128, + Experience: 0, + Creatures: []*Creature{}, + } + + res, err := vp.db.Exec(`insert into velouremon_players (chatid, player, health, experience) values (?, ?, ?, ?);`, + player.ChatID, player.Name, player.Health, player.Experience) + if err != nil { + log.Error().Err(err) + return nil, err + } + + id, err := res.LastInsertId() + if err != nil { + log.Error().Err(err) + return nil, err + } + player.ID = id + vp.players = append(vp.players, player) + return player, nil +} + +func (vp *VelouremonPlugin) savePlayer(player *Player) error { + _, err := vp.db.Exec(`update velouremon_players set health = ?, experience = ? where id = ?;`, player.ID, player.Health, player.Experience) + if err != nil { + log.Error().Err(err) + return err + } + err = vp.savePlayerCreatures(player) + if err != nil { + log.Error().Err(err) + return err + } + return nil +} diff --git a/plugins/velouremon/velouremon.go b/plugins/velouremon/velouremon.go new file mode 100644 index 0000000..e9ebff1 --- /dev/null +++ b/plugins/velouremon/velouremon.go @@ -0,0 +1,80 @@ +package velouremon + +import ( + "strings" + "time" + + "github.com/jmoiron/sqlx" + + "github.com/velour/catbase/bot" + "github.com/velour/catbase/bot/msg" +) + +type VelouremonHandler func(bot.Connector, *Player, []string) bool + +type VelouremonPlugin struct { + bot bot.Bot + db *sqlx.DB + channel string + handlers map[string]VelouremonHandler + threads map[string]*Interaction + players []*Player + creatures []*Creature + abilities []*Ability + timer *time.Timer +} + +func New(b bot.Bot) *VelouremonPlugin { + dur, _ := time.ParseDuration("15m") + timer := time.NewTimer(dur) + + vp := &VelouremonPlugin{ + bot: b, + db: b.DB(), + channel: "", + handlers: map[string]VelouremonHandler{}, + threads: map[string]*Interaction{}, + players: []*Player{}, + creatures: []*Creature{}, + abilities: []*Ability{}, + timer: timer, + } + + vp.checkAndBuildDBOrFail() + vp.loadFromDB() + + vp.handlers["status"] = vp.handleStatus + + b.Register(vp, bot.Message, vp.message) + b.Register(vp, bot.Help, vp.help) + + return vp +} + +func (vp *VelouremonPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { + if !message.Command { + return false + } + + if vp.channel == "" { + vp.channel = message.Channel + } + + tokens := strings.Fields(message.Body) + command := strings.ToLower(tokens[0]) + + if fun, ok := vp.handlers[command]; ok { + player, err := vp.getOrAddPlayer(message.User) + if err != nil { + return false + } + return fun(c, player, tokens[1:]) + } + + return false +} + +func (vp *VelouremonPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { + vp.bot.Send(c, bot.Message, message.Channel, "try something else, this is too complicated for you.") + return true +}