Compare commits

...

2 Commits

16 changed files with 380 additions and 54 deletions

2
.gitignore vendored
View File

@ -1 +1,3 @@
.vscode/launch.json .vscode/launch.json
cmd/tins2020/*rice-box.*

View File

@ -49,6 +49,7 @@ func (a *Animation) Run() {
} }
a.active = true a.active = true
a.start = time.Now() a.start = time.Now()
a.lastUpdate = 0
} }
func (a *Animation) SetInterval(interval time.Duration) { func (a *Animation) SetInterval(interval time.Duration) {

View File

@ -9,6 +9,9 @@ import (
"opslag.de/schobers/tins2020" "opslag.de/schobers/tins2020"
) )
//go:generate go get -u github.com/GeertJohan/go.rice/rice
//go:generate rice embed
func main() { func main() {
err := run() err := run()
if err != nil { if err != nil {
@ -96,7 +99,7 @@ func run() error {
app := tins2020.NewContainer() app := tins2020.NewContainer()
overlays := tins2020.NewContainer() overlays := tins2020.NewContainer()
dialogs := tins2020.NewDialogs() dialogs := tins2020.NewDialogs(game)
overlays.AddChild(dialogs) overlays.AddChild(dialogs)
overlays.AddChild(&tins2020.FPS{Show: &game.Debug}) overlays.AddChild(&tins2020.FPS{Show: &game.Debug})
@ -112,7 +115,7 @@ func run() error {
if err != nil { if err != nil {
return err return err
} }
dialogs.ShowIntro() dialogs.ShowIntro(ctx)
w, h := window.GetSize() w, h := window.GetSize()
app.Arrange(ctx, tins2020.Rect(0, 0, w, h)) app.Arrange(ctx, tins2020.Rect(0, 0, w, h))

View File

@ -11,6 +11,14 @@ var Transparent = sdl.Color{R: 0, G: 0, B: 0, A: 0}
var TransparentWhite = sdl.Color{R: 255, G: 255, B: 255, A: 31} var TransparentWhite = sdl.Color{R: 255, G: 255, B: 255, A: 31}
var White = sdl.Color{R: 255, G: 255, B: 255, A: 255} var White = sdl.Color{R: 255, G: 255, B: 255, A: 255}
func HexColor(s string) (sdl.Color, error) {
c, err := img.HexColor(s)
if err != nil {
return sdl.Color{}, err
}
return sdl.Color(c), nil
}
func MustHexColor(s string) sdl.Color { return sdl.Color(img.MustHexColor(s)) } func MustHexColor(s string) sdl.Color { return sdl.Color(img.MustHexColor(s)) }
func SetDrawColor(renderer *sdl.Renderer, color sdl.Color) { func SetDrawColor(renderer *sdl.Renderer, color sdl.Color) {

View File

@ -22,6 +22,7 @@ func EmptyEvent(fn EventFn) EventContextFn {
type ControlBase struct { type ControlBase struct {
Bounds Rectangle Bounds Rectangle
IsDisabled bool
IsMouseOver bool IsMouseOver bool
OnLeftMouseButtonClick EventContextFn OnLeftMouseButtonClick EventContextFn
@ -32,30 +33,27 @@ func (b *ControlBase) Arrange(ctx *Context, bounds Rectangle) { b.Bounds = bound
func (b *ControlBase) Init(*Context) error { return nil } func (b *ControlBase) Init(*Context) error { return nil }
func (b *ControlBase) Handle(ctx *Context, event sdl.Event) bool { func (b *ControlBase) Handle(ctx *Context, event sdl.Event) bool {
b.HandleNoFeedback(ctx, event)
return false
}
func (b *ControlBase) HandleNoFeedback(ctx *Context, event sdl.Event) {
switch e := event.(type) { switch e := event.(type) {
case *sdl.MouseMotionEvent: case *sdl.MouseMotionEvent:
b.IsMouseOver = b.Bounds.IsPointInside(e.X, e.Y) b.IsMouseOver = b.Bounds.IsPointInside(e.X, e.Y)
case *sdl.MouseButtonEvent: case *sdl.MouseButtonEvent:
if b.IsMouseOver && e.Button == sdl.BUTTON_LEFT && e.Type == sdl.MOUSEBUTTONDOWN { if !b.IsDisabled && b.IsMouseOver && e.Button == sdl.BUTTON_LEFT && e.Type == sdl.MOUSEBUTTONDOWN {
b.Invoke(ctx, b.OnLeftMouseButtonClick) return b.Invoke(ctx, b.OnLeftMouseButtonClick)
} }
case *sdl.WindowEvent: case *sdl.WindowEvent:
if e.Event == sdl.WINDOWEVENT_LEAVE { if e.Event == sdl.WINDOWEVENT_LEAVE {
b.IsMouseOver = false b.IsMouseOver = false
} }
} }
return false
} }
func (b *ControlBase) Invoke(ctx *Context, fn EventContextFn) { func (b *ControlBase) Invoke(ctx *Context, fn EventContextFn) bool {
if fn == nil { if fn == nil {
return return false
} }
fn(ctx) fn(ctx)
return true
} }
func (b *ControlBase) Render(*Context) {} func (b *ControlBase) Render(*Context) {}

View File

@ -5,25 +5,37 @@ type Dialogs struct {
intro Control intro Control
settings Control settings Control
research Control
dialogClosed *Events dialogClosed *Events
dialogOpened *Events dialogOpened *Events
} }
func NewDialogs() *Dialogs { func NewDialogs(game *Game) *Dialogs {
return &Dialogs{ return &Dialogs{
intro: &Intro{},
settings: &LargeDialog{},
research: NewResearch(game),
dialogClosed: NewEvents(), dialogClosed: NewEvents(),
dialogOpened: NewEvents(), dialogOpened: NewEvents(),
} }
} }
func (d *Dialogs) showDialog(ctx *Context, control Control) {
d.SetContent(ctx, control)
control.(Dialog).ShowDialog(ctx, d.Close)
d.dialogOpened.Notify(nil)
}
func (d *Dialogs) Arrange(ctx *Context, bounds Rectangle) {
d.Proxy.Arrange(ctx, bounds)
}
func (d *Dialogs) DialogClosed() EventHandler { return d.dialogClosed } func (d *Dialogs) DialogClosed() EventHandler { return d.dialogClosed }
func (d *Dialogs) DialogOpened() EventHandler { return d.dialogOpened } func (d *Dialogs) DialogOpened() EventHandler { return d.dialogOpened }
func (d *Dialogs) Init(ctx *Context) error { func (d *Dialogs) Init(ctx *Context) error {
d.intro = &Intro{}
d.settings = &DialogBase{}
err := d.intro.Init(ctx) err := d.intro.Init(ctx)
if err != nil { if err != nil {
return err return err
@ -32,22 +44,23 @@ func (d *Dialogs) Init(ctx *Context) error {
if err != nil { if err != nil {
return err return err
} }
err = d.research.Init(ctx)
return nil return nil
} }
func (d *Dialogs) Close() { func (d *Dialogs) Close() {
d.Proxied = nil d.SetContent(nil, nil)
d.dialogClosed.Notify(nil) d.dialogClosed.Notify(nil)
} }
func (d *Dialogs) ShowIntro() { func (d *Dialogs) ShowIntro(ctx *Context) {
d.Proxied = d.intro d.showDialog(ctx, d.intro)
d.intro.(Dialog).ShowDialog(d.Close)
d.dialogOpened.Notify(nil)
} }
func (d *Dialogs) ShowSettings() { func (d *Dialogs) ShowResearch(ctx *Context) {
d.Proxied = d.settings d.showDialog(ctx, d.research)
d.settings.(Dialog).ShowDialog(d.Close) }
d.dialogOpened.Notify(nil)
func (d *Dialogs) ShowSettings(ctx *Context) {
d.showDialog(ctx, d.settings)
} }

15
game.go
View File

@ -163,6 +163,11 @@ func (g *Game) Load() {
func (g *Game) Pause() { g.setSpeed(GameSpeedPaused) } func (g *Game) Pause() { g.setSpeed(GameSpeedPaused) }
func (g *Game) PlantFlower(id string, tile Point) { func (g *Game) PlantFlower(id string, tile Point) {
if g.Terrain.HasFlower(tile) {
// TODO: notify user it tried to plant on tile with flower?
return
}
flower, ok := g.Herbarium.Find(id) flower, ok := g.Herbarium.Find(id)
if !ok { if !ok {
log.Println("user was able to plant a flower that doesn't exist") log.Println("user was able to plant a flower that doesn't exist")
@ -213,10 +218,6 @@ func (g *Game) SelectShovel() {
g.selectTool(&ShovelTool{}) g.selectTool(&ShovelTool{})
} }
func (g *Game) SelectResearch() {
g.Pause()
}
func (g *Game) SpeedChanged() EventHandler { return g.speedChanged } func (g *Game) SpeedChanged() EventHandler { return g.speedChanged }
func (g *Game) State() GameState { func (g *Game) State() GameState {
@ -258,6 +259,12 @@ func (g *Game) Tool() Tool { return g.tool }
func (g *Game) ToolChanged() EventHandler { return g.toolChanged } func (g *Game) ToolChanged() EventHandler { return g.toolChanged }
func (g *Game) UnlockNextFlower() {
price := g.Herbarium.UnlockNext()
g.Balance -= price
g.selectTool(nil)
}
func (g *Game) Update() { func (g *Game) Update() {
for g.simulation.Animate() { for g.simulation.Animate() {
g.tick() g.tick()

View File

@ -87,7 +87,10 @@ func (c *GameControls) Init(ctx *Context) error {
c.game.SpeedChanged().RegisterItf(c.speedChanged) c.game.SpeedChanged().RegisterItf(c.speedChanged)
c.game.ToolChanged().RegisterItf(c.toolChanged) c.game.ToolChanged().RegisterItf(c.toolChanged)
c.dialogs.DialogOpened().Register(func() { c.game.Pause() }) c.dialogs.DialogOpened().Register(func() { c.game.Pause() })
c.dialogs.DialogClosed().Register(func() { c.game.Resume() }) c.dialogs.DialogClosed().Register(func() {
c.updateFlowerControls(ctx)
c.game.Resume()
})
c.flowers.Background = MustHexColor("#356dad") c.flowers.Background = MustHexColor("#356dad")
c.flowers.ButtonLength = 64 c.flowers.ButtonLength = 64
@ -117,7 +120,10 @@ func (c *GameControls) Init(ctx *Context) error {
c.menu.Background = MustHexColor("#356dad") c.menu.Background = MustHexColor("#356dad")
c.menu.Buttons = []Control{ c.menu.Buttons = []Control{
NewIconButton("control-settings", func(*Context) { c.dialogs.ShowSettings() }), NewIconButtonConfig("control-settings", c.dialogs.ShowSettings, func(b *IconButton) {
b.IsDisabled = true
b.IconDisabled = "#afafaf"
}),
NewIconButton("control-save", func(*Context) { c.game.Save() }), NewIconButton("control-save", func(*Context) { c.game.Save() }),
NewIconButton("control-load", func(ctx *Context) { NewIconButton("control-load", func(ctx *Context) {
c.game.Load() c.game.Load()
@ -127,11 +133,11 @@ func (c *GameControls) Init(ctx *Context) error {
c.game.New() c.game.New()
c.updateFlowerControls(ctx) c.updateFlowerControls(ctx)
}), }),
NewIconButton("control-information", func(*Context) { c.dialogs.ShowIntro() }), NewIconButton("control-information", c.dialogs.ShowIntro),
} }
c.shovel = NewIconButtonConfig("control-shovel", func(*Context) { c.game.SelectShovel() }, func(b *IconButton) { b.IconHeight = 32 }) c.shovel = NewIconButtonConfig("control-shovel", func(*Context) { c.game.SelectShovel() }, func(b *IconButton) { b.IconHeight = 32 })
c.research = NewIconButtonConfig("control-research", func(*Context) { c.game.SelectResearch() }, func(b *IconButton) { b.IconHeight = 32 }) c.research = NewIconButtonConfig("control-research", c.dialogs.ShowResearch, func(b *IconButton) { b.IconHeight = 32 })
c.otherTools.Buttons = []Control{c.shovel, c.research} c.otherTools.Buttons = []Control{c.shovel, c.research}
c.Container.AddChild(&c.menu) c.Container.AddChild(&c.menu)
@ -160,10 +166,10 @@ func (c *GameControls) Handle(ctx *Context, event sdl.Event) bool {
case sdl.K_d: case sdl.K_d:
c.game.SelectShovel() c.game.SelectShovel()
case sdl.K_r: case sdl.K_r:
c.game.SelectResearch() c.dialogs.ShowResearch(ctx)
case sdl.K_ESCAPE: case sdl.K_ESCAPE:
if c.game.Tool() == nil { if c.game.Tool() == nil {
c.dialogs.ShowIntro() c.dialogs.ShowIntro(ctx)
} else { } else {
c.game.CancelTool() c.game.CancelTool()
} }

View File

@ -36,7 +36,7 @@ func (h *Herbarium) Reset() {
BuyPrice: 100, BuyPrice: 100,
SellPrice: 20, SellPrice: 20,
Traits: NewLoosestrifeTraits(), Traits: NewLoosestrifeTraits(),
Unlocked: true, Unlocked: false,
}) })
h.Add("coneflower", FlowerDescriptor{ h.Add("coneflower", FlowerDescriptor{
Name: "Coneflower", Name: "Coneflower",
@ -45,7 +45,7 @@ func (h *Herbarium) Reset() {
BuyPrice: 500, BuyPrice: 500,
SellPrice: 100, SellPrice: 100,
Traits: NewConeflowerTraits(), Traits: NewConeflowerTraits(),
Unlocked: true, Unlocked: false,
}) })
h.Add("tulip", FlowerDescriptor{ h.Add("tulip", FlowerDescriptor{
Name: "Tulip", Name: "Tulip",
@ -54,7 +54,7 @@ func (h *Herbarium) Reset() {
BuyPrice: 20000, BuyPrice: 20000,
SellPrice: 5000, SellPrice: 5000,
Traits: NewTulipTraits(), Traits: NewTulipTraits(),
Unlocked: true, Unlocked: false,
}) })
h.Add("ajuga", FlowerDescriptor{ h.Add("ajuga", FlowerDescriptor{
Name: "Ajuga", Name: "Ajuga",
@ -63,7 +63,7 @@ func (h *Herbarium) Reset() {
BuyPrice: 100000, BuyPrice: 100000,
SellPrice: 10000, SellPrice: 10000,
Traits: NewAjugaTraits(), Traits: NewAjugaTraits(),
Unlocked: true, Unlocked: false,
}) })
} }
@ -114,3 +114,28 @@ func (h *Herbarium) IsUnlocked(id string) bool {
} }
return flower.Unlocked return flower.Unlocked
} }
func (h *Herbarium) NextFlowerToUnlock() (string, FlowerDescriptor, int) {
var previous *FlowerDescriptor
for _, id := range h.Flowers() {
flower, _ := h.Find(id)
if !flower.Unlocked {
if previous == nil {
return "", FlowerDescriptor{}, 0
}
return id, flower, previous.BuyPrice * 2
}
previous = &flower
}
return "", FlowerDescriptor{}, 0
}
func (h *Herbarium) UnlockNext() int {
id, flower, price := h.NextFlowerToUnlock()
if len(id) == 0 {
return 0
}
flower.Unlocked = true
h.flowers[id] = flower
return price
}

View File

@ -20,7 +20,6 @@ type IconButton struct {
IconHover HoverEffect IconHover HoverEffect
IsActive bool IsActive bool
IsDisabled bool
} }
func NewIconButton(icon string, onClick EventContextFn) *IconButton { func NewIconButton(icon string, onClick EventContextFn) *IconButton {
@ -44,6 +43,16 @@ func (b *IconButton) activeTexture(ctx *Context) *Texture {
if texture != nil { if texture != nil {
return texture return texture
} }
texture = ctx.Textures.Texture(b.Icon)
if len(b.IconDisabled) == 0 {
return texture
}
color, err := HexColor(b.IconDisabled)
if err == nil {
texture.SetColor(color)
}
return texture
} }
return ctx.Textures.Texture(b.Icon) return ctx.Textures.Texture(b.Icon)
} }

View File

@ -48,10 +48,6 @@ type Paragraph struct {
Label Label
} }
// func (p *Paragraph) Arrange(ctx *Context, bounds Rectangle) {
// p.Label.Arrange(ctx, bounds)
// }
func (p *Paragraph) Render(ctx *Context) { func (p *Paragraph) Render(ctx *Context) {
font := ctx.Fonts.Font(p.fontName()) font := ctx.Fonts.Font(p.fontName())
color := p.fontColor() color := p.fontColor()

View File

@ -6,12 +6,14 @@ type DialogBase struct {
Container Container
content Proxy content Proxy
onShow *Events
close EventFn close EventFn
} }
type Dialog interface { type Dialog interface {
CloseDialog() CloseDialog()
ShowDialog(EventFn) OnShow() EventHandler
ShowDialog(*Context, EventFn)
} }
func (d *DialogBase) CloseDialog() { func (d *DialogBase) CloseDialog() {
@ -21,17 +23,27 @@ func (d *DialogBase) CloseDialog() {
} }
} }
func (d *DialogBase) Init(ctx *Context) error {
d.AddChild(&d.content)
return d.Container.Init(ctx)
}
func (d *DialogBase) OnShow() EventHandler {
if d.onShow == nil {
d.onShow = NewEvents()
}
return d.onShow
}
func (d *DialogBase) SetContent(control Control) { func (d *DialogBase) SetContent(control Control) {
d.content.Proxied = control d.content.Proxied = control
} }
func (d *DialogBase) ShowDialog(close EventFn) { func (d *DialogBase) ShowDialog(ctx *Context, close EventFn) {
d.close = close d.close = close
if d.onShow != nil {
d.onShow.Notify(ctx)
} }
func (d *DialogBase) Init(ctx *Context) error {
d.AddChild(&d.content)
return d.Container.Init(ctx)
} }
type LargeDialog struct { type LargeDialog struct {
@ -93,3 +105,5 @@ func (d *LargeDialog) Render(ctx *Context) {
d.DialogBase.Render(ctx) d.DialogBase.Render(ctx)
} }
func (d *LargeDialog) SetCaption(s string) { d.title.Text = s }

5
map.go
View File

@ -24,6 +24,11 @@ func (m *Map) DigFlower(pos Point) string {
return flower.ID return flower.ID
} }
func (m *Map) HasFlower(pos Point) bool {
_, ok := m.Flowers[pos]
return ok
}
func (m *Map) NewFlower(pos Point, id string, traits FlowerTraits) Flower { func (m *Map) NewFlower(pos Point, id string, traits FlowerTraits) Flower {
flower := Flower{ flower := Flower{
ID: id, ID: id,

View File

@ -6,9 +6,12 @@ var _ Control = &Proxy{}
type Proxy struct { type Proxy struct {
Proxied Control Proxied Control
bounds Rectangle
} }
func (p *Proxy) Arrange(ctx *Context, bounds Rectangle) { func (p *Proxy) Arrange(ctx *Context, bounds Rectangle) {
p.bounds = bounds
if p.Proxied == nil { if p.Proxied == nil {
return return
} }
@ -35,3 +38,11 @@ func (p *Proxy) Render(ctx *Context) {
} }
p.Proxied.Render(ctx) p.Proxied.Render(ctx)
} }
func (p *Proxy) SetContent(ctx *Context, content Control) {
p.Proxied = content
if content == nil {
return
}
content.Arrange(ctx, p.bounds)
}

227
research.go Normal file
View File

@ -0,0 +1,227 @@
package tins2020
import (
"fmt"
"math"
"math/rand"
"strconv"
"strings"
"time"
"github.com/veandco/go-sdl2/sdl"
)
type Research struct {
Container
game *Game
botanist Specialist
farmer Specialist
typing string
digitCount int
close func()
description Paragraph
specialists Paragraph
input Label
digits []Digit
animate Animation
}
func NewResearch(game *Game) Control {
research := &Research{
game: game,
animate: NewAnimation(20 * time.Millisecond),
}
dialog := &LargeDialog{}
dialog.SetCaption("Research")
dialog.SetContent(research)
dialog.OnShow().RegisterItf(func(state interface{}) {
research.onShow(state.(*Context))
})
research.close = func() { dialog.CloseDialog() }
return dialog
}
type Digit struct {
ControlBase
Value string
highlight int
}
func (d *Digit) Render(ctx *Context) {
font := ctx.Fonts.Font("title")
color := White
if d.highlight > 0 {
color = MustHexColor("#15569F")
}
font.RenderCopyAlign(ctx.Renderer, d.Value, Pt(d.Bounds.X+d.Bounds.W/2, d.Bounds.Y+int32(font.Height())), color, TextAlignmentCenter)
}
func (d *Digit) Blink() {
d.highlight = 4
}
func (d *Digit) Tick() {
if d.highlight > 0 {
d.highlight--
}
}
type Specialist struct {
Cost int
Number string
}
func (r *Research) Init(ctx *Context) error {
r.AddChild(&r.description)
r.AddChild(&r.specialists)
r.AddChild(&r.input)
r.description.Text = "Call a specialist to conduct research with."
r.digits = make([]Digit, 10)
for i := range r.digits {
r.digits[i].Value = strconv.Itoa(i)
r.AddChild(&r.digits[i])
}
return nil
}
func (r *Research) Arrange(ctx *Context, bounds Rectangle) {
r.Container.Arrange(ctx, bounds)
r.specialists.Arrange(ctx, RectSize(r.Bounds.X, r.Bounds.Y+40, r.Bounds.W, r.Bounds.H-40))
r.input.Arrange(ctx, RectSize(r.Bounds.X, r.Bounds.X+r.Bounds.H-48, r.Bounds.W, 24))
r.input.Alignment = TextAlignmentCenter
center := Pt(r.Bounds.X+r.Bounds.W/2, r.Bounds.Y+r.Bounds.H/2)
distance := float64(bounds.H) * .3
for i := range r.digits {
angle := (float64((10-i)%10)*0.16 + .2) * math.Pi
pos := Pt(int32(distance*math.Cos(angle)), int32(.8*distance*math.Sin(angle)))
digitCenter := center.Add(pos)
r.digits[i].Arrange(ctx, RectSize(digitCenter.X-24, digitCenter.Y-24, 48, 48))
}
}
func (r *Research) userTyped(i int) {
r.digits[i].Blink()
digit := strconv.Itoa(i)
if len(r.typing) == 0 || digit != r.typing {
r.typing = digit
r.digitCount = 1
} else {
r.digitCount++
}
if !strings.HasPrefix(r.botanist.Number, r.input.Text+r.typing) {
r.input.Text = ""
r.typing = ""
r.digitCount = 0
} else if r.digitCount == i || r.digitCount == 10 {
r.input.Text += digit
r.typing = ""
r.digitCount = 0
if r.input.Text == r.botanist.Number {
r.game.UnlockNextFlower()
r.close()
r.input.Text = ""
}
}
}
func (r *Research) Handle(ctx *Context, event sdl.Event) bool {
switch e := event.(type) {
case *sdl.KeyboardEvent:
if e.Type == sdl.KEYDOWN {
switch e.Keysym.Sym {
case sdl.K_0:
r.userTyped(0)
case sdl.K_KP_0:
r.userTyped(0)
case sdl.K_1:
r.userTyped(1)
case sdl.K_KP_1:
r.userTyped(1)
case sdl.K_2:
r.userTyped(2)
case sdl.K_KP_2:
r.userTyped(2)
case sdl.K_3:
r.userTyped(3)
case sdl.K_KP_3:
r.userTyped(3)
case sdl.K_4:
r.userTyped(4)
case sdl.K_KP_4:
r.userTyped(4)
case sdl.K_5:
r.userTyped(5)
case sdl.K_KP_5:
r.userTyped(5)
case sdl.K_6:
r.userTyped(6)
case sdl.K_KP_6:
r.userTyped(6)
case sdl.K_7:
r.userTyped(7)
case sdl.K_KP_7:
r.userTyped(7)
case sdl.K_8:
r.userTyped(8)
case sdl.K_KP_8:
r.userTyped(8)
case sdl.K_9:
r.userTyped(9)
case sdl.K_KP_9:
r.userTyped(9)
}
}
}
return false
}
func (r *Research) Render(ctx *Context) {
for i := range r.digits {
r.digits[i].Tick()
}
r.Container.Render(ctx)
}
func (r *Research) onShow(ctx *Context) {
generateNumber := func() string {
var number string
for i := 0; i < 3; i++ {
number += strconv.Itoa(rand.Intn(9) + 1)
}
return number
}
r.digitCount = 0
r.input.Text = ""
var specialists string
defer func() {
r.specialists.Text = specialists
}()
_, _, price := r.game.Herbarium.NextFlowerToUnlock()
if price == 0 {
specialists += "Botanist (unlocks next flower; unavailable)\n"
specialists += "Farmer (fertilizes land; unavailable)\n"
return
}
r.botanist.Cost = price
if r.game.Balance < r.botanist.Cost {
r.botanist.Number = "**unavailable**"
} else {
r.botanist.Number = generateNumber()
}
specialists += fmt.Sprintf("Botanist: no. %s (unlocks next flower; $ %d)\n", r.botanist.Number, r.botanist.Cost)
specialists += "Farmer: no. **unavailable** (fertilizes land; $ ---)\n"
}

View File

@ -34,7 +34,7 @@ func (r *terrainRenderer) Init(ctx *Context) error {
func isControlKeyDown() bool { func isControlKeyDown() bool {
state := sdl.GetKeyboardState() state := sdl.GetKeyboardState()
return state[sdl.SCANCODE_LCTRL] == 1 || state[sdl.SCANCODE_RCTRL] == 1 return state[sdl.SCANCODE_LCTRL] == 1 || state[sdl.SCANCODE_RCTRL] == 1 || state[sdl.SCANCODE_LGUI] == 1 || state[sdl.SCANCODE_RGUI] == 1
} }
func (r *terrainRenderer) Handle(ctx *Context, event sdl.Event) bool { func (r *terrainRenderer) Handle(ctx *Context, event sdl.Event) bool {
@ -42,12 +42,13 @@ func (r *terrainRenderer) Handle(ctx *Context, event sdl.Event) bool {
case *sdl.MouseButtonEvent: case *sdl.MouseButtonEvent:
if r.project.windowInteractRect.IsPointInside(e.X, e.Y) { if r.project.windowInteractRect.IsPointInside(e.X, e.Y) {
if e.Type == sdl.MOUSEBUTTONDOWN { if e.Type == sdl.MOUSEBUTTONDOWN {
if e.Button == sdl.BUTTON_MIDDLE || (e.Button == sdl.BUTTON_LEFT && isControlKeyDown()) { controlKeyDown := isControlKeyDown()
if e.Button == sdl.BUTTON_MIDDLE || (e.Button == sdl.BUTTON_LEFT && controlKeyDown) {
if !r.drag.IsDragging() { if !r.drag.IsDragging() {
r.drag.Start(Pt(e.X, e.Y)) r.drag.Start(Pt(e.X, e.Y))
} }
} }
if e.Button == sdl.BUTTON_LEFT { if e.Button == sdl.BUTTON_LEFT && !controlKeyDown {
pos := r.project.screenToMapInt(e.X, e.Y) pos := r.project.screenToMapInt(e.X, e.Y)
r.game.UserClickedTile(pos) r.game.UserClickedTile(pos)
} }