some basic interactions

This commit is contained in:
skiesel 2019-07-07 20:28:06 -04:00
parent e97d0ab323
commit 46ac787c06
11 changed files with 340 additions and 44 deletions

View File

@ -16,7 +16,7 @@ func (vp *VelouremonPlugin) loadAbilities() error {
for rows.Next() { for rows.Next() {
ability := &Ability{} ability := &Ability{}
err := rows.Scan(ability) err := rows.StructScan(ability)
if err != nil { if err != nil {
log.Error().Err(err) log.Error().Err(err)
return err return err
@ -49,7 +49,7 @@ func (vp *VelouremonPlugin) loadAbilityRefsForCreature(ref *CreatureRef) ([]*Abi
abilities := []*AbilityRef{} abilities := []*AbilityRef{}
for rows.Next() { for rows.Next() {
ability := &AbilityRef{} ability := &AbilityRef{}
err := rows.Scan(ability) err := rows.StructScan(ability)
if err != nil { if err != nil {
log.Error().Err(err) log.Error().Err(err)
@ -82,3 +82,12 @@ func (vp *VelouremonPlugin) loadAbilityFromRef(ref *AbilityRef) (*Ability, error
} }
return ability, nil return ability, nil
} }
func (vp *VelouremonPlugin) saveNewAbility(ability *Ability) error {
_, err := vp.db.Exec(`insert into velouremon_abilities (name, damage, heal, shield, weaken, critical) values (?, ?, ?, ?, ?, ?);`, ability.Name, ability.Damage, ability.Heal, ability.Shield, ability.Weaken, ability.Critical)
if err != nil {
log.Error().Err(err)
return err
}
return nil
}

View File

@ -1,10 +1,89 @@
package velouremon package velouremon
import ( import (
"strconv"
"github.com/velour/catbase/bot" "github.com/velour/catbase/bot"
) )
func (vp *VelouremonPlugin) handleStatus(c bot.Connector, player *Player, tokens []string) bool { func min(a, b int) int {
if a < b {
return a
}
return b
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
func (vp *VelouremonPlugin) handleStatus(c bot.Connector, player *Player) bool {
vp.bot.Send(c, bot.Message, vp.channel, player.string()) vp.bot.Send(c, bot.Message, vp.channel, player.string())
return true return true
} }
func (vp *VelouremonPlugin) handleAddCreature(c bot.Connector, tokens []string) bool {
if len(tokens) == 3 {
name := tokens[0]
stats := make([]int, 2)
fail := false
for i := range stats {
stat, err := strconv.Atoi(tokens[i+1])
if err != nil {
fail = true
break
}
stats[i] = min(max(stat, 0), 255)
}
if !fail {
err := vp.saveNewCreature(&Creature{
Name: name,
Defense: stats[0],
Attack: stats[1],
})
if err == nil {
vp.bot.Send(c, bot.Message, vp.channel, "Added " + name)
return true
}
}
}
vp.bot.Send(c, bot.Message, vp.channel, "!add_creature [name] [defense 0-255] [attack 0-255]")
return true
}
func (vp *VelouremonPlugin) handleAddAbility(c bot.Connector, tokens []string) bool {
if len(tokens) == 6 {
name := tokens[0]
stats := make([]int, 5)
fail := false
for i := range stats {
stat, err := strconv.Atoi(tokens[i+1])
if err != nil {
fail = true
break
}
stats[i] = min(max(stat, 0), 255)
}
if !fail {
err := vp.saveNewAbility(&Ability{
Name: name,
Damage: stats[0],
Heal: stats[1],
Shield: stats[2],
Weaken: stats[3],
Critical: stats[4],
})
if err == nil {
vp.bot.Send(c, bot.Message, vp.channel, "Added " + name)
return true
}
}
}
vp.bot.Send(c, bot.Message, vp.channel, "!add_ability [name] [damage 0-255] [heal 0-255] [shield 0-255] [weaken 0-255] [critical 0-255]")
return true
}

View File

@ -1,6 +1,8 @@
package velouremon package velouremon
import ( import (
"math/rand"
"math"
"fmt" "fmt"
) )
@ -28,3 +30,25 @@ func (c *Creature) string() string {
} }
return message return message
} }
func (vp *VelouremonPlugin) buildOutCreature(c *Creature) *Creature {
creature := &Creature{
ID: c.ID,
Name: c.Name,
Health: 255,
Experience: int(math.Max(1, rand.NormFloat64() * 100 + 250)),
Defense: c.Defense,
Attack: c.Attack,
Abilities: make([]*Ability, rand.Intn(4)),
}
used := map[int]int{}
for i := range creature.Abilities {
index := rand.Intn(len(vp.abilities))
for _, ok := used[index]; ok; _, ok = used[index] {
index = rand.Intn(len(vp.abilities))
}
creature.Abilities[i] = vp.abilities[index]
}
return creature
}

View File

@ -19,11 +19,13 @@ func (vp *VelouremonPlugin) loadCreatures() error {
Health: 255, Health: 255,
Experience: 0, Experience: 0,
} }
err := rows.Scan(creature) err := rows.StructScan(creature)
log.Print(err)
if err != nil { if err != nil {
log.Error().Err(err) log.Error().Err(err)
return err return err
} }
vp.creatures = append(vp.creatures, creature) vp.creatures = append(vp.creatures, creature)
} }
return nil return nil
@ -52,7 +54,7 @@ func (vp *VelouremonPlugin) loadCreatureRefsForPlayer(player *Player) ([]*Creatu
creatures := []*CreatureRef{} creatures := []*CreatureRef{}
for rows.Next() { for rows.Next() {
creature := &CreatureRef{} creature := &CreatureRef{}
err := rows.Scan(creature) err := rows.StructScan(creature)
if err != nil { if err != nil {
log.Error().Err(err) log.Error().Err(err)
@ -114,3 +116,12 @@ func (vp *VelouremonPlugin) saveCreatureForPlayer(player *Player, creature *Crea
} }
return nil return nil
} }
func (vp *VelouremonPlugin) saveNewCreature(creature *Creature) error {
_, err := vp.db.Exec(`insert into velouremon_creatures (name, defense, attack) values (?, ?, ?);`, creature.Name, creature.Defense, creature.Attack)
if err != nil {
log.Error().Err(err)
return err
}
return nil
}

View File

@ -5,54 +5,65 @@ import (
) )
func (vp *VelouremonPlugin) checkAndBuildDBOrFail() { func (vp *VelouremonPlugin) checkAndBuildDBOrFail() {
dbNeedsPopulating := false
if rows, err := vp.db.Queryx(`select name from sqlite_master where type='table' and name='velouremon_players';`); err != nil {
log.Fatal().Err(err)
} else {
dbNeedsPopulating = rows.Next()
}
if _, err := vp.db.Exec(`create table if not exists velouremon_players ( if _, err := vp.db.Exec(`create table if not exists velouremon_players (
id integer primary key, id integer primary key,
chatid string, chatid string,
player string, player string,
health integer, health integer,
experience integer experience integer
);`); err != nil { );`); err != nil {
log.Fatal().Err(err) log.Fatal().Err(err)
} }
if _, err := vp.db.Exec(`create table if not exists velouremon_creature_ref ( if _, err := vp.db.Exec(`create table if not exists velouremon_creature_ref (
id integer primary key, id integer primary key,
player integer, player integer,
creature integer, creature integer,
health integer, health integer,
experience integer experience integer
);`); err != nil { );`); err != nil {
log.Fatal().Err(err) log.Fatal().Err(err)
} }
if _, err := vp.db.Exec(`create table if not exists velouremon_creatures ( if _, err := vp.db.Exec(`create table if not exists velouremon_creatures (
id integer primary key, id integer primary key,
creature string, name string,
defense integer, defense integer,
attack integer attack integer
);`); err != nil { );`); err != nil {
log.Fatal().Err(err) log.Fatal().Err(err)
} }
if _, err := vp.db.Exec(`create table if not exists velouremon_ability_ref ( if _, err := vp.db.Exec(`create table if not exists velouremon_ability_ref (
id integer primary key, id integer primary key,
creatureref integer, creatureref integer,
ability integer ability integer
);`); err != nil { );`); err != nil {
log.Fatal().Err(err) log.Fatal().Err(err)
} }
if _, err := vp.db.Exec(`create table if not exists velouremon_abilities ( if _, err := vp.db.Exec(`create table if not exists velouremon_abilities (
id integer primary key, id integer primary key,
name string, name string,
damage int, damage int,
heal int, heal int,
shield int, shield int,
weaken int, weaken int,
critical int critical int
);`); err != nil { );`); err != nil {
log.Fatal().Err(err) log.Fatal().Err(err)
} }
if dbNeedsPopulating {
vp.populateDBWithBaseData()
}
} }
func (vp *VelouremonPlugin) loadFromDB() { func (vp *VelouremonPlugin) loadFromDB() {

View File

@ -2,6 +2,7 @@ package velouremon
import ( import (
"fmt" "fmt"
"strings"
"math/rand" "math/rand"
"time" "time"
@ -9,8 +10,10 @@ import (
) )
type Interaction struct { type Interaction struct {
id string
players []*Player players []*Player
creatures []*Creature creatures []*Creature
started bool
} }
func randomInteraction(c bot.Connector, vp *VelouremonPlugin) { func randomInteraction(c bot.Connector, vp *VelouremonPlugin) {
@ -19,10 +22,56 @@ func randomInteraction(c bot.Connector, vp *VelouremonPlugin) {
if vp.channel != "" { if vp.channel != "" {
creature := vp.creatures[rand.Intn(len(vp.creatures))] creature := vp.creatures[rand.Intn(len(vp.creatures))]
message := fmt.Sprintf("A wild %s appeared.", creature.Name) message := fmt.Sprintf("A wild %s appeared.", creature.Name)
vp.bot.Send(c, bot.Message, vp.channel, message) id, _ := vp.bot.Send(c, bot.Message, vp.channel, message)
vp.threads[id] = &Interaction {
id: id,
players: []*Player{},
creatures: []*Creature{
vp.buildOutCreature(creature),
},
started: false,
}
vp.bot.Send(c, bot.Reply, vp.channel, "A wild %s appeared.", id)
} }
dur, _ := time.ParseDuration("1h") vp.timer.Reset(1 * time.Hour)
vp.timer.Reset(dur)
} }
} }
func (i *Interaction) handleMessage(vp *VelouremonPlugin, c bot.Connector, player *Player, tokens []string) bool {
if len(tokens) > 0 {
command := strings.ToLower(tokens[0])
if command == "join" {
return i.handleJoin(vp, c, player)
} else if command == "run" {
return i.handleRun(vp, c, player)
}
}
return false
}
func (i *Interaction) handleJoin(vp *VelouremonPlugin, c bot.Connector, player *Player) bool {
for _, p := range i.players {
if player == p {
vp.bot.Send(c, bot.Reply, vp.channel, player.Name + " is already in the party.", i.id)
return true
}
}
i.players = append(i.players, player)
vp.bot.Send(c, bot.Reply, vp.channel, player.Name + " has just joined the party.", i.id)
return true
}
func (i *Interaction) handleRun(vp *VelouremonPlugin, c bot.Connector, player *Player) bool {
for index, p := range i.players {
if player == p {
i.players = append(i.players[:index], i.players[index+1:]...)
vp.bot.Send(c, bot.Reply, vp.channel, player.Name + " has just left the party.", i.id)
return true
}
}
vp.bot.Send(c, bot.Reply, vp.channel, player.Name + " is not currently in the party.", i.id)
return true
}

View File

@ -27,7 +27,7 @@ func (vp *VelouremonPlugin) getOrAddPlayer(u *user.User) (*Player, error) {
} }
func (p *Player) string() string { func (p *Player) string() string {
message := fmt.Sprintf("%s : %d HP, %d XP\n", p.Name, p.Health, p.Experience) message := fmt.Sprintf("%s: %d HP, %d XP\n", p.Name, p.Health, p.Experience)
for _, creature := range p.Creatures { for _, creature := range p.Creatures {
message += "\t" + strings.ReplaceAll(creature.string(), "\n", "\n\t") message += "\t" + strings.ReplaceAll(creature.string(), "\n", "\n\t")
message = strings.TrimSuffix(message, "\t") message = strings.TrimSuffix(message, "\t")

View File

@ -16,7 +16,7 @@ func (vp *VelouremonPlugin) loadPlayers() error {
for rows.Next() { for rows.Next() {
player := &Player{} player := &Player{}
err := rows.Scan(player) err := rows.StructScan(player)
if err != nil { if err != nil {
log.Error().Err(err) log.Error().Err(err)
return err return err
@ -35,7 +35,7 @@ func (vp *VelouremonPlugin) addPlayer(p *user.User) (*Player, error) {
player := &Player{ player := &Player{
ChatID: p.ID, ChatID: p.ID,
Name: p.Name, Name: p.Name,
Health: 128, Health: 255,
Experience: 0, Experience: 0,
Creatures: []*Creature{}, Creatures: []*Creature{},
} }

View File

@ -0,0 +1,17 @@
package velouremon
func (vp *VelouremonPlugin) populateDBWithBaseData() {
vp.db.Exec(`insert into velouremon_creatures (name, defense, attack) values (?, ?, ?);`,
"Lap Sprite", 10, 5)
vp.db.Exec(`insert into velouremon_creatures (name, defense, attack) values (?, ?, ?);`,
"Industry Rep", 5, 10)
vp.db.Exec(`insert into velouremon_creatures (name, defense, attack) values (?, ?, ?);`,
"Charpov", 10, 10)
vp.db.Exec(`insert into velouremon_abilities (name, damage, heal, shield, weaken, critical) values (?, ?, ?, ?, ?, ?);`,
"Procrastinate", 0, 0, 10, 0, 0)
vp.db.Exec(`insert into velouremon_abilities (name, damage, heal, shield, weaken, critical) values (?, ?, ?, ?, ?, ?);`,
"Defend", 0, 0, 5, 0, 0)
vp.db.Exec(`insert into velouremon_abilities (name, damage, heal, shield, weaken, critical) values (?, ?, ?, ?, ?, ?);`,
"Graduate", 0, 255, 0, 0, 0)
}

View File

@ -10,13 +10,10 @@ import (
"github.com/velour/catbase/bot/msg" "github.com/velour/catbase/bot/msg"
) )
type VelouremonHandler func(bot.Connector, *Player, []string) bool
type VelouremonPlugin struct { type VelouremonPlugin struct {
bot bot.Bot bot bot.Bot
db *sqlx.DB db *sqlx.DB
channel string channel string
handlers map[string]VelouremonHandler
threads map[string]*Interaction threads map[string]*Interaction
players []*Player players []*Player
creatures []*Creature creatures []*Creature
@ -25,29 +22,26 @@ type VelouremonPlugin struct {
} }
func New(b bot.Bot) *VelouremonPlugin { func New(b bot.Bot) *VelouremonPlugin {
dur, _ := time.ParseDuration("15m")
timer := time.NewTimer(dur)
vp := &VelouremonPlugin{ vp := &VelouremonPlugin{
bot: b, bot: b,
db: b.DB(), db: b.DB(),
channel: "", channel: "",
handlers: map[string]VelouremonHandler{},
threads: map[string]*Interaction{}, threads: map[string]*Interaction{},
players: []*Player{}, players: []*Player{},
creatures: []*Creature{}, creatures: []*Creature{},
abilities: []*Ability{}, abilities: []*Ability{},
timer: timer, timer: time.NewTimer(15 * time.Minute),
} }
vp.checkAndBuildDBOrFail() vp.checkAndBuildDBOrFail()
vp.loadFromDB() vp.loadFromDB()
vp.handlers["status"] = vp.handleStatus
b.Register(vp, bot.Message, vp.message) b.Register(vp, bot.Message, vp.message)
b.Register(vp, bot.Reply, vp.replyMessage)
b.Register(vp, bot.Help, vp.help) b.Register(vp, bot.Help, vp.help)
go randomInteraction(b.DefaultConnector(), vp)
return vp return vp
} }
@ -63,17 +57,43 @@ func (vp *VelouremonPlugin) message(c bot.Connector, kind bot.Kind, message msg.
tokens := strings.Fields(message.Body) tokens := strings.Fields(message.Body)
command := strings.ToLower(tokens[0]) command := strings.ToLower(tokens[0])
if fun, ok := vp.handlers[command]; ok { if command == "status" {
player, err := vp.getOrAddPlayer(message.User) player, err := vp.getOrAddPlayer(message.User)
if err != nil { if err != nil {
return false return false
} }
return fun(c, player, tokens[1:]) return vp.handleStatus(c, player)
} else if len(tokens) > 1 {
if command == "add_creature" {
return vp.handleAddCreature(c, tokens[1:])
} else if command == "add_ability" {
return vp.handleAddAbility(c, tokens[1:])
}
} }
return false return false
} }
func (vp *VelouremonPlugin) replyMessage(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool {
if !message.Command {
return false
}
identifier := args[0].(string)
if strings.ToLower(message.User.Name) != strings.ToLower(vp.bot.Config().Get("Nick", "bot")) {
if interaction, ok := vp.threads[identifier]; ok {
player, err := vp.getOrAddPlayer(message.User)
if err != nil {
return false
}
tokens := strings.Fields(message.Body)
return interaction.handleMessage(vp, c, player, tokens)
}
}
return false
}
func (vp *VelouremonPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...interface{}) bool { 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.") vp.bot.Send(c, bot.Message, message.Channel, "try something else, this is too complicated for you.")
return true return true

View File

@ -0,0 +1,76 @@
package velouremon
import (
"github.com/velour/catbase/plugins/cli"
"os"
"strings"
"time"
"testing"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/stretchr/testify/assert"
"github.com/velour/catbase/bot"
"github.com/velour/catbase/bot/msg"
"github.com/velour/catbase/bot/user"
)
func init() {
log.Logger = log.Logger.Output(zerolog.ConsoleWriter{Out: os.Stderr})
}
func makeMessageBy(payload, by string) (bot.Connector, bot.Kind, msg.Message) {
isCmd := strings.HasPrefix(payload, "!")
if isCmd {
payload = payload[1:]
}
return &cli.CliPlugin{}, bot.Message, msg.Message{
User: &user.User{Name: by},
Channel: "test",
Body: payload,
Command: isCmd,
}
}
func makeMessage(payload string) (bot.Connector, bot.Kind, msg.Message) {
return makeMessageBy(payload, "tester")
}
func setup(t *testing.T) (*VelouremonPlugin, *bot.MockBot) {
mb := bot.NewMockBot()
r := New(mb)
r.channel = "test"
return r, mb
}
func TestStatus(t *testing.T) {
c, mb := setup(t)
res := c.message(makeMessage("!status"))
assert.True(t, res)
assert.Len(t, mb.Messages, 1)
assert.Contains(t, mb.Messages[0], "tester: 255 HP, 0 XP")
}
func TestSimpleAppeared(t *testing.T) {
c, mb := setup(t)
c.timer.Reset(1 * time.Nanosecond)
time.Sleep(1 * time.Millisecond)
assert.Len(t, mb.Messages, 1)
assert.Contains(t, mb.Messages[0], "A wild")
}
func TestAddCreature(t *testing.T) {
c, mb := setup(t)
res := c.message(makeMessage("!add_creature NewCreature 0 0"))
assert.True(t, res)
assert.Len(t, mb.Messages, 1)
assert.Contains(t, mb.Messages[0], "Added NewCreature")
}
func TestAddAbility(t *testing.T) {
c, mb := setup(t)
res := c.message(makeMessage("!add_ability NewAbility 0 0 0 0 0"))
assert.True(t, res)
assert.Len(t, mb.Messages, 1)
assert.Contains(t, mb.Messages[0], "Added NewAbility")
}