Compare commits
4 Commits
c27d43e323
...
c926ae2ef9
Author | SHA1 | Date | |
---|---|---|---|
c926ae2ef9 | |||
013fe4bdb6 | |||
f57a9dd845 | |||
76ac685cbb |
12
README.md
12
README.md
@ -23,13 +23,21 @@ In Botanim you play the role of botanist and your goal is to cultivate flowers i
|
||||
Flowers can only grow (well) in certain climates based on two properties: humidity and temperature. Watch out for existing vegetation to get an idea how humid the land is and check the appearance of the tile to see how hot it is. When well placed your planted flower will spread soon but an odd choice might kill your flower almost instantly. So choose carefully. When the flower spread significantly you can dig up flowers again to collect more money.
|
||||
|
||||
**Controls:**
|
||||
- D: Selects shovel
|
||||
- H: Selects shovel
|
||||
- R: Selects research
|
||||
- Spacebar: pauses game
|
||||
- 1: runs game at normal speed
|
||||
- 2: runs game extra fast
|
||||
- Mouse wheel or plus/minus: zooms landscape
|
||||
- CTRL + left mouse button or middle mouse button: pans landscape
|
||||
- W, A, S, D keys or CTRL + left mouse button or middle mouse button: pans landscape
|
||||
|
||||
** On screen **
|
||||
On the left side of the playing screen you'll find several buttons:
|
||||
- Settings: disabled.
|
||||
- Save: saves the game instantly, only single slot.
|
||||
- Load: loads previously saved game instantly.
|
||||
- New: starts a new game with a different terrain.
|
||||
- Information: shows the intro/information screen (again, also accessible with the Escape key).
|
||||
|
||||
Have fun playing!
|
||||
|
||||
|
@ -21,7 +21,7 @@ type BuyFlowerButton struct {
|
||||
|
||||
func NewBuyFlowerButton(icon, iconDisabled, flowerID string, flower FlowerDescriptor, onClick EventContextFn) *BuyFlowerButton {
|
||||
return &BuyFlowerButton{
|
||||
IconButton: *NewIconButtonConfig(icon, onClick, func(b *IconButton) {
|
||||
IconButton: *NewIconButtonConfigure(icon, onClick, func(b *IconButton) {
|
||||
b.IconDisabled = iconDisabled
|
||||
b.IsDisabled = !flower.Unlocked
|
||||
}),
|
||||
|
@ -136,6 +136,8 @@ func run() error {
|
||||
app.Arrange(ctx, tins2020.RectAbs(0, 0, w, h))
|
||||
ctx.Settings.Window.Size = tins2020.PtPtr(w, h)
|
||||
}
|
||||
case *sdl.MouseMotionEvent:
|
||||
ctx.MousePosition = tins2020.Pt(e.X, e.Y)
|
||||
}
|
||||
app.Handle(ctx, event)
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ type Context struct {
|
||||
Resources Resources
|
||||
Textures Textures
|
||||
Settings Settings
|
||||
MousePosition Point
|
||||
ShouldQuit bool
|
||||
}
|
||||
|
||||
|
23
control.go
23
control.go
@ -22,12 +22,35 @@ func EmptyEvent(fn EventFn) EventContextFn {
|
||||
type ControlBase struct {
|
||||
Bounds Rectangle
|
||||
|
||||
FontName string
|
||||
Foreground sdl.Color
|
||||
|
||||
IsDisabled bool
|
||||
IsMouseOver bool
|
||||
|
||||
OnLeftMouseButtonClick EventContextFn
|
||||
}
|
||||
|
||||
func (c *ControlBase) ActualForeground() sdl.Color {
|
||||
var none sdl.Color
|
||||
if c.Foreground == none {
|
||||
return MustHexColor("#ffffff")
|
||||
}
|
||||
return c.Foreground
|
||||
}
|
||||
|
||||
func (c *ControlBase) ActualFont(ctx *Context) *Font {
|
||||
name := c.ActualFontName()
|
||||
return ctx.Fonts.Font(name)
|
||||
}
|
||||
|
||||
func (c *ControlBase) ActualFontName() string {
|
||||
if len(c.FontName) == 0 {
|
||||
return "default"
|
||||
}
|
||||
return c.FontName
|
||||
}
|
||||
|
||||
func (b *ControlBase) Arrange(ctx *Context, bounds Rectangle) { b.Bounds = bounds }
|
||||
|
||||
func (b *ControlBase) Init(*Context) error { return nil }
|
||||
|
8
game.go
8
game.go
@ -114,8 +114,16 @@ func (g *Game) Dig(tile Point) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
adjacent := g.Terrain.FlowersOnAdjacentTiles(tile)
|
||||
switch adjacent {
|
||||
case 3:
|
||||
g.Balance += (desc.SellPrice * 3 / 2) // 50% bonus
|
||||
case 4:
|
||||
g.Balance += (desc.SellPrice * 2) // 100% bonus
|
||||
default:
|
||||
g.Balance += desc.SellPrice
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Game) New() {
|
||||
g.Pause()
|
||||
|
@ -100,44 +100,61 @@ func (c *GameControls) Init(ctx *Context) error {
|
||||
}
|
||||
|
||||
c.top.Orientation = OrientationHorizontal
|
||||
c.pause = NewIconButtonConfig("control-pause", EmptyEvent(func() {
|
||||
c.pause = NewIconButtonConfigure("control-pause", EmptyEvent(func() {
|
||||
c.game.Pause()
|
||||
}), func(b *IconButton) {
|
||||
b.IconDisabled = "control-pause-disabled"
|
||||
b.Tooltip.Text = "Pause game"
|
||||
})
|
||||
c.run = NewIconButtonConfig("control-run", EmptyEvent(func() {
|
||||
c.run = NewIconButtonConfigure("control-run", EmptyEvent(func() {
|
||||
c.game.Run()
|
||||
}), func(b *IconButton) {
|
||||
b.IconDisabled = "control-run-disabled"
|
||||
b.Tooltip.Text = "Run game at normal speed"
|
||||
})
|
||||
c.runFast = NewIconButtonConfig("control-run-fast", EmptyEvent(func() {
|
||||
c.runFast = NewIconButtonConfigure("control-run-fast", EmptyEvent(func() {
|
||||
c.game.RunFast()
|
||||
}), func(b *IconButton) {
|
||||
b.IconDisabled = "control-run-fast-disabled"
|
||||
b.Tooltip.Text = "Run game at fast speed"
|
||||
})
|
||||
c.speedChanged(c.game.Speed)
|
||||
c.top.Buttons = []Control{c.pause, c.run, c.runFast}
|
||||
|
||||
c.menu.Background = MustHexColor("#356dad")
|
||||
c.menu.Buttons = []Control{
|
||||
NewIconButtonConfig("control-settings", c.dialogs.ShowSettings, func(b *IconButton) {
|
||||
NewIconButtonConfigure("control-settings", c.dialogs.ShowSettings, func(b *IconButton) {
|
||||
b.IsDisabled = true
|
||||
b.IconDisabled = "#afafaf"
|
||||
}),
|
||||
NewIconButton("control-save", func(*Context) { c.game.Save() }),
|
||||
NewIconButton("control-load", func(ctx *Context) {
|
||||
NewIconButtonConfigure("control-save", func(*Context) { c.game.Save() }, func(b *IconButton) {
|
||||
b.Tooltip.Text = "Save game (overwrites previous save; no confirmation)"
|
||||
}),
|
||||
NewIconButtonConfigure("control-load", func(ctx *Context) {
|
||||
c.game.Load()
|
||||
c.updateFlowerControls(ctx)
|
||||
}, func(b *IconButton) {
|
||||
b.Tooltip.Text = "Load last saved game (no confirmation)"
|
||||
}),
|
||||
NewIconButton("control-new", func(ctx *Context) {
|
||||
NewIconButtonConfigure("control-new", func(ctx *Context) {
|
||||
c.game.New()
|
||||
c.updateFlowerControls(ctx)
|
||||
}, func(b *IconButton) {
|
||||
b.Tooltip.Text = "Start new game (no confirmation)"
|
||||
}),
|
||||
NewIconButtonConfigure("control-information", c.dialogs.ShowIntro, func(b *IconButton) {
|
||||
b.Tooltip.Text = "Show information/intro"
|
||||
}),
|
||||
NewIconButton("control-information", c.dialogs.ShowIntro),
|
||||
}
|
||||
|
||||
c.shovel = NewIconButtonConfig("control-shovel", func(*Context) { c.game.SelectShovel() }, func(b *IconButton) { b.IconHeight = 32 })
|
||||
c.research = NewIconButtonConfig("control-research", c.dialogs.ShowResearch, func(b *IconButton) { b.IconHeight = 32 })
|
||||
c.shovel = NewIconButtonConfigure("control-shovel", func(*Context) { c.game.SelectShovel() }, func(b *IconButton) {
|
||||
b.IconHeight = 32
|
||||
b.Tooltip.Text = "Select harvest tool (key: H)"
|
||||
})
|
||||
c.research = NewIconButtonConfigure("control-research", c.dialogs.ShowResearch, func(b *IconButton) {
|
||||
b.IconHeight = 32
|
||||
b.Tooltip.Text = "Conduct research (key: R)"
|
||||
})
|
||||
c.otherTools.Buttons = []Control{c.shovel, c.research}
|
||||
|
||||
c.Container.AddChild(&c.menu)
|
||||
@ -163,7 +180,7 @@ func (c *GameControls) Handle(ctx *Context, event sdl.Event) bool {
|
||||
c.game.Run()
|
||||
case sdl.K_2:
|
||||
c.game.RunFast()
|
||||
case sdl.K_d:
|
||||
case sdl.K_h:
|
||||
c.game.SelectShovel()
|
||||
case sdl.K_r:
|
||||
c.dialogs.ShowResearch(ctx)
|
||||
|
@ -1,5 +1,9 @@
|
||||
package tins2020
|
||||
|
||||
import (
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
type HoverEffect int
|
||||
|
||||
const (
|
||||
@ -19,6 +23,7 @@ type IconButton struct {
|
||||
IconActive HoverEffect
|
||||
IconHover HoverEffect
|
||||
|
||||
Tooltip Tooltip
|
||||
IsActive bool
|
||||
}
|
||||
|
||||
@ -31,7 +36,7 @@ func NewIconButton(icon string, onClick EventContextFn) *IconButton {
|
||||
}
|
||||
}
|
||||
|
||||
func NewIconButtonConfig(icon string, onClick EventContextFn, configure func(*IconButton)) *IconButton {
|
||||
func NewIconButtonConfigure(icon string, onClick EventContextFn, configure func(*IconButton)) *IconButton {
|
||||
button := NewIconButton(icon, onClick)
|
||||
configure(button)
|
||||
return button
|
||||
@ -57,6 +62,31 @@ func (b *IconButton) activeTexture(ctx *Context) *Texture {
|
||||
return ctx.Textures.Texture(b.Icon)
|
||||
}
|
||||
|
||||
func (b *IconButton) Arrange(ctx *Context, bounds Rectangle) {
|
||||
b.ControlBase.Arrange(ctx, bounds)
|
||||
b.Tooltip.Arrange(ctx, bounds)
|
||||
}
|
||||
|
||||
func (b *IconButton) Handle(ctx *Context, event sdl.Event) bool {
|
||||
if b.ControlBase.Handle(ctx, event) {
|
||||
return true
|
||||
}
|
||||
if b.Tooltip.Handle(ctx, event) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (b *IconButton) Init(ctx *Context) error {
|
||||
if err := b.ControlBase.Init(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := b.Tooltip.Init(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *IconButton) Render(ctx *Context) {
|
||||
iconTexture := b.activeTexture(ctx)
|
||||
|
||||
@ -81,6 +111,10 @@ func (b *IconButton) Render(ctx *Context) {
|
||||
ctx.Renderer.FillRect(b.Bounds.SDLPtr())
|
||||
}
|
||||
iconTexture.SetColor(White)
|
||||
|
||||
if len(b.Tooltip.Text) > 0 && b.IsMouseOver {
|
||||
b.Tooltip.Render(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
type Scale int
|
||||
|
4
intro.go
4
intro.go
@ -12,13 +12,13 @@ func (i *Intro) Init(ctx *Context) error {
|
||||
"In Botanim you play the role of botanist and your goal is to cultivate flowers in an open landscape.\n\n" +
|
||||
"Flowers can only grow (well) in certain climates based on two properties: humidity and temperature. Watch out for existing vegetation to get an idea how humid the land is and check the appearance of the tile to see how hot it is. When well placed your planted flower will spread soon but an odd choice might kill your flower almost instantly. So choose carefully. When the flower spread significantly you can dig up flowers again to collect more money.\n\n" +
|
||||
"Controls:\n" +
|
||||
" - D: Selects shovel\n" +
|
||||
" - H: Selects shovel\n" +
|
||||
" - R: Selects research\n" +
|
||||
" - Spacebar: pauses game\n" +
|
||||
" - 1: runs game at normal speed\n" +
|
||||
" - 2: runs game extra fast\n" +
|
||||
" - Mouse wheel or plus/minus: zooms landscape\n" +
|
||||
" - CTRL + left mouse button or middle mouse button: pans landscape\n" +
|
||||
" - W, A, S, D keys or CTRL + left mouse button or middle mouse button: pans landscape\n" +
|
||||
"\n" +
|
||||
"Have fun playing!"
|
||||
i.SetContent(&i.welcome)
|
||||
|
27
label.go
27
label.go
@ -2,37 +2,18 @@ package tins2020
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
type Label struct {
|
||||
ControlBase
|
||||
|
||||
FontColor sdl.Color
|
||||
FontName string
|
||||
Text string
|
||||
Alignment TextAlignment
|
||||
}
|
||||
|
||||
func (l *Label) fontColor() sdl.Color {
|
||||
var none sdl.Color
|
||||
if l.FontColor == none {
|
||||
return MustHexColor("#ffffff")
|
||||
}
|
||||
return l.FontColor
|
||||
}
|
||||
|
||||
func (l *Label) fontName() string {
|
||||
if len(l.FontName) == 0 {
|
||||
return "default"
|
||||
}
|
||||
return l.FontName
|
||||
}
|
||||
|
||||
func (l *Label) Render(ctx *Context) {
|
||||
font := ctx.Fonts.Font(l.fontName())
|
||||
color := l.fontColor()
|
||||
font := ctx.Fonts.Font(l.ActualFontName())
|
||||
color := l.ActualForeground()
|
||||
bottom := l.Bounds.Y + l.Bounds.H
|
||||
switch l.Alignment {
|
||||
case TextAlignmentCenter:
|
||||
@ -49,8 +30,8 @@ type Paragraph struct {
|
||||
}
|
||||
|
||||
func (p *Paragraph) Render(ctx *Context) {
|
||||
font := ctx.Fonts.Font(p.fontName())
|
||||
color := p.fontColor()
|
||||
font := ctx.Fonts.Font(p.ActualFontName())
|
||||
color := p.ActualForeground()
|
||||
fontHeight := int32(font.Height())
|
||||
lines := strings.Split(p.Text, "\n")
|
||||
|
||||
|
@ -62,11 +62,10 @@ func (d *LargeDialog) Arrange(ctx *Context, bounds Rectangle) {
|
||||
}
|
||||
|
||||
func (d *LargeDialog) Init(ctx *Context) error {
|
||||
d.title = Label{
|
||||
Text: "Botanim",
|
||||
FontName: "title",
|
||||
Alignment: TextAlignmentCenter,
|
||||
}
|
||||
d.title.Text = "Botanim"
|
||||
d.title.FontName = "title"
|
||||
d.title.Alignment = TextAlignmentCenter
|
||||
|
||||
d.close = IconButton{
|
||||
Icon: "control-cancel",
|
||||
IconHover: HoverEffectColor,
|
||||
|
17
map.go
17
map.go
@ -15,6 +15,23 @@ func (m *Map) AddFlower(pos Point, id string, traits FlowerTraits) {
|
||||
m.Flowers[pos] = m.NewFlower(pos, id, traits)
|
||||
}
|
||||
|
||||
func (m *Map) FlowersOnAdjacentTiles(pos Point) int {
|
||||
var count int
|
||||
if _, ok := m.Flowers[Pt(pos.X+1, pos.Y)]; ok {
|
||||
count++
|
||||
}
|
||||
if _, ok := m.Flowers[Pt(pos.X-1, pos.Y)]; ok {
|
||||
count++
|
||||
}
|
||||
if _, ok := m.Flowers[Pt(pos.X, pos.Y+1)]; ok {
|
||||
count++
|
||||
}
|
||||
if _, ok := m.Flowers[Pt(pos.X, pos.Y-1)]; ok {
|
||||
count++
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func (m *Map) DigFlower(pos Point) string {
|
||||
flower, ok := m.Flowers[pos]
|
||||
if !ok {
|
||||
|
@ -101,6 +101,20 @@ func (p *projection) visibleTiles(action func(int32, int32, Point)) {
|
||||
}
|
||||
}
|
||||
|
||||
func (p *projection) Pan(ctx *Context, delta PointF) {
|
||||
p.center = p.center.Add(delta.Mul(p.zoomInv))
|
||||
p.update(ctx.Renderer)
|
||||
}
|
||||
|
||||
func (p *projection) SetZoom(ctx *Context, center PointF, zoom float32) {
|
||||
if p.zoom == zoom {
|
||||
return
|
||||
}
|
||||
p.center = center.Sub(center.Sub(p.center).Mul(p.zoom / zoom))
|
||||
p.zoom = zoom
|
||||
p.update(ctx.Renderer)
|
||||
}
|
||||
|
||||
func (p *projection) ZoomOut(ctx *Context, center PointF) {
|
||||
if p.zoom <= .25 {
|
||||
return
|
||||
@ -114,12 +128,3 @@ func (p *projection) ZoomIn(ctx *Context, center PointF) {
|
||||
}
|
||||
p.SetZoom(ctx, center, 2*p.zoom)
|
||||
}
|
||||
|
||||
func (p *projection) SetZoom(ctx *Context, center PointF, zoom float32) {
|
||||
if p.zoom == zoom {
|
||||
return
|
||||
}
|
||||
p.center = center.Sub(center.Sub(p.center).Mul(p.zoom / zoom))
|
||||
p.zoom = zoom
|
||||
p.update(ctx.Renderer)
|
||||
}
|
||||
|
@ -101,6 +101,14 @@ func (r *terrainRenderer) Handle(ctx *Context, event sdl.Event) bool {
|
||||
r.project.ZoomOut(ctx, r.project.center)
|
||||
case sdl.K_KP_MINUS:
|
||||
r.project.ZoomOut(ctx, r.project.center)
|
||||
case sdl.K_w:
|
||||
r.project.Pan(ctx, PtF(-1, -1))
|
||||
case sdl.K_a:
|
||||
r.project.Pan(ctx, PtF(-1, 1))
|
||||
case sdl.K_s:
|
||||
r.project.Pan(ctx, PtF(1, 1))
|
||||
case sdl.K_d:
|
||||
r.project.Pan(ctx, PtF(1, -1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
59
tooltip.go
Normal file
59
tooltip.go
Normal file
@ -0,0 +1,59 @@
|
||||
package tins2020
|
||||
|
||||
import "github.com/veandco/go-sdl2/sdl"
|
||||
|
||||
type Tooltip struct {
|
||||
ControlBase
|
||||
|
||||
Text string
|
||||
}
|
||||
|
||||
const tooltipBorderThickness = 1
|
||||
const tooltipHorizontalPadding = 4
|
||||
const tooltipMouseDistance = 12
|
||||
|
||||
func (t *Tooltip) Handle(ctx *Context, event sdl.Event) bool {
|
||||
if len(t.Text) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
font := ctx.Fonts.Font(t.ActualFontName())
|
||||
windowW, windowH, err := ctx.Renderer.GetOutputSize()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
labelW, labelH, err := font.SizeUTF8(t.Text)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
mouse := ctx.MousePosition
|
||||
width := int32(labelW) + 2*tooltipBorderThickness + 2*tooltipHorizontalPadding
|
||||
height := int32(labelH) + 2*tooltipBorderThickness
|
||||
|
||||
left := mouse.X + tooltipMouseDistance
|
||||
top := mouse.Y + tooltipMouseDistance
|
||||
if left+width > windowW {
|
||||
left = mouse.X - tooltipMouseDistance - width
|
||||
}
|
||||
if top+height > windowH {
|
||||
top = mouse.Y - tooltipMouseDistance - height
|
||||
}
|
||||
|
||||
t.Bounds = Rect(left, top, width, height)
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *Tooltip) Render(ctx *Context) {
|
||||
SetDrawColor(ctx.Renderer, Black)
|
||||
ctx.Renderer.FillRect(t.Bounds.SDLPtr())
|
||||
|
||||
SetDrawColor(ctx.Renderer, White)
|
||||
ctx.Renderer.DrawRect(t.Bounds.SDLPtr())
|
||||
|
||||
font := t.ActualFont(ctx)
|
||||
|
||||
bottomLeft := Pt(t.Bounds.X+tooltipBorderThickness+tooltipHorizontalPadding, t.Bounds.Y+t.Bounds.H-tooltipBorderThickness)
|
||||
font.RenderCopy(ctx.Renderer, t.Text, bottomLeft, White)
|
||||
}
|
Loading…
Reference in New Issue
Block a user