Compare commits

...

6 Commits

18 changed files with 564 additions and 140 deletions

View File

@ -10,9 +10,8 @@ import (
type BuyFlowerButton struct { type BuyFlowerButton struct {
IconButton IconButton
Name string FlowerID string
Price int Flower FlowerDescriptor
Description string
hoverAnimation *Animation hoverAnimation *Animation
hoverOffset int32 hoverOffset int32
@ -20,27 +19,33 @@ type BuyFlowerButton struct {
priceTexture *Texture priceTexture *Texture
} }
func NewBuyFlowerButton(icon, iconDisabled, name string, price int, description string, isDisabled bool, onClick EventFn) *BuyFlowerButton { func NewBuyFlowerButton(icon, iconDisabled, flowerID string, flower FlowerDescriptor, onClick EventContextFn) *BuyFlowerButton {
return &BuyFlowerButton{ return &BuyFlowerButton{
IconButton: *NewIconButtonConfig(icon, onClick, func(b *IconButton) { IconButton: *NewIconButtonConfig(icon, onClick, func(b *IconButton) {
b.IconDisabled = iconDisabled b.IconDisabled = iconDisabled
b.IsDisabled = isDisabled b.IsDisabled = !flower.Unlocked
}), }),
Name: name, FlowerID: flowerID,
Price: price, Flower: flower,
Description: description,
} }
} }
func (b *BuyFlowerButton) animate() { func (b *BuyFlowerButton) animate() {
b.hoverOffset++ b.hoverOffset++
if b.hoverOffset > b.hoverTexture.Size().X+b.Bounds.W { if b.hoverOffset > b.hoverTexture.Size().X+b.Bounds.W {
b.hoverOffset = b.priceTexture.Size().X b.hoverOffset = 0
} }
} }
func (b *BuyFlowerButton) fmtTooltipText() string {
if b.Flower.Unlocked {
return fmt.Sprintf("%s - %s - %s", FmtMoney(b.Flower.BuyPrice), b.Flower.Name, "Traits are not known yet.")
}
return fmt.Sprintf("%s - %s - %s", FmtMoney(b.Flower.BuyPrice), b.Flower.Name, b.Flower.Description)
}
func (b *BuyFlowerButton) Init(ctx *Context) error { func (b *BuyFlowerButton) Init(ctx *Context) error {
text := fmt.Sprintf("%s - %s - %s", FmtMoney(b.Price), b.Name, b.Description) text := fmt.Sprintf("%s - %s - %s", FmtMoney(b.Flower.BuyPrice), b.Flower.Name, b.Flower.Description)
font := ctx.Fonts.Font("small") font := ctx.Fonts.Font("small")
color := MustHexColor("#ffffff") color := MustHexColor("#ffffff")
texture, err := font.Render(ctx.Renderer, text, color) texture, err := font.Render(ctx.Renderer, text, color)
@ -48,7 +53,7 @@ func (b *BuyFlowerButton) Init(ctx *Context) error {
return err return err
} }
b.hoverTexture = texture b.hoverTexture = texture
texture, err = font.Render(ctx.Renderer, FmtMoney(b.Price), color) texture, err = font.Render(ctx.Renderer, FmtMoney(b.Flower.BuyPrice), color)
if err != nil { if err != nil {
return err return err
} }
@ -72,7 +77,7 @@ func (b *BuyFlowerButton) Render(ctx *Context) {
pos := Pt(b.Bounds.X, b.Bounds.Y) pos := Pt(b.Bounds.X, b.Bounds.Y)
iconTexture.CopyResize(ctx.Renderer, RectSize(pos.X, pos.Y-40, buttonBarWidth, 120)) iconTexture.CopyResize(ctx.Renderer, RectSize(pos.X, pos.Y-40, buttonBarWidth, 120))
if b.IsMouseOver && !b.IsDisabled { if (b.IsMouseOver && !b.IsDisabled) || b.IsActive {
mouseOverTexture.Copy(ctx.Renderer, pos) mouseOverTexture.Copy(ctx.Renderer, pos)
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -4,7 +4,7 @@ control-research: images/genericItem_color_111.png
control-settings: images/gear.png control-settings: images/gear.png
control-save: images/save.png control-save: images/save.png
control-load: images/basket.png control-load: images/import.png
control-quit: images/power.png control-quit: images/power.png
control-pause: images/pause.png control-pause: images/pause.png
@ -64,32 +64,32 @@ bush-large-2: images/plant_bushLarge_NW.png
bush-large-3: images/plant_bushLarge_SW.png bush-large-3: images/plant_bushLarge_SW.png
bush-large-4: images/plant_bushLarge_SE.png bush-large-4: images/plant_bushLarge_SE.png
flower-poppy-disabled: images/flower_yellowC_NE_disabled.png flower-anemone-disabled: images/flower_yellowC_NE_disabled.png
flower-poppy-1: images/flower_yellowC_NE.png flower-anemone-1: images/flower_yellowC_NE.png
flower-poppy-2: images/flower_yellowC_NW.png flower-anemone-2: images/flower_yellowC_NW.png
flower-poppy-3: images/flower_yellowC_SW.png flower-anemone-3: images/flower_yellowC_SW.png
flower-poppy-4: images/flower_yellowC_SE.png flower-anemone-4: images/flower_yellowC_SE.png
flower-red-c-disabled: images/flower_redC_NE_disabled.png flower-loosestrife-disabled: images/flower_redC_NE_disabled.png
flower-red-c-1: images/flower_redC_NE.png flower-loosestrife-1: images/flower_redC_NE.png
flower-red-c-2: images/flower_redC_NW.png flower-loosestrife-2: images/flower_redC_NW.png
flower-red-c-3: images/flower_redC_SW.png flower-loosestrife-3: images/flower_redC_SW.png
flower-red-c-4: images/flower_redC_SE.png flower-loosestrife-4: images/flower_redC_SE.png
flower-red-a-disabled: images/flower_redA_NE_disabled.png flower-tulip-disabled: images/flower_redA_NE_disabled.png
flower-red-a-1: images/flower_redA_NE.png flower-tulip-1: images/flower_redA_NE.png
flower-red-a-2: images/flower_redA_NW.png flower-tulip-2: images/flower_redA_NW.png
flower-red-a-3: images/flower_redA_SW.png flower-tulip-3: images/flower_redA_SW.png
flower-red-a-4: images/flower_redA_SE.png flower-tulip-4: images/flower_redA_SE.png
flower-purple-a-disabled: images/flower_purpleA_NE_disabled.png flower-ajuga-disabled: images/flower_purpleA_NE_disabled.png
flower-purple-a-1: images/flower_purpleA_NE.png flower-ajuga-1: images/flower_purpleA_NE.png
flower-purple-a-2: images/flower_purpleA_NW.png flower-ajuga-2: images/flower_purpleA_NW.png
flower-purple-a-3: images/flower_purpleA_SW.png flower-ajuga-3: images/flower_purpleA_SW.png
flower-purple-a-4: images/flower_purpleA_SE.png flower-ajuga-4: images/flower_purpleA_SE.png
flower-purple-c-disabled: images/flower_purpleC_NE_disabled.png flower-coneflower-disabled: images/flower_purpleC_NE_disabled.png
flower-purple-c-1: images/flower_purpleC_NE.png flower-coneflower-1: images/flower_purpleC_NE.png
flower-purple-c-2: images/flower_purpleC_NW.png flower-coneflower-2: images/flower_purpleC_NW.png
flower-purple-c-3: images/flower_purpleC_SW.png flower-coneflower-3: images/flower_purpleC_SW.png
flower-purple-c-4: images/flower_purpleC_SE.png flower-coneflower-4: images/flower_purpleC_SE.png

View File

@ -47,7 +47,14 @@ func run() error {
if ctx.Settings.Window.Size == nil { if ctx.Settings.Window.Size == nil {
ctx.Settings.Window.Size = tins2020.PtPtr(800, 600) ctx.Settings.Window.Size = tins2020.PtPtr(800, 600)
} }
if ctx.Settings.Window.VSync == nil {
vsync := true
ctx.Settings.Window.VSync = &vsync
}
if *ctx.Settings.Window.VSync {
sdl.SetHint(sdl.HINT_RENDER_VSYNC, "1")
}
sdl.SetHint(sdl.HINT_RENDER_SCALE_QUALITY, "1") sdl.SetHint(sdl.HINT_RENDER_SCALE_QUALITY, "1")
window, err := sdl.CreateWindow("TINS 2020", window, err := sdl.CreateWindow("TINS 2020",
ctx.Settings.Window.Location.X, ctx.Settings.Window.Location.Y, ctx.Settings.Window.Location.X, ctx.Settings.Window.Location.Y,
@ -93,7 +100,7 @@ func run() error {
content := tins2020.NewContainer() content := tins2020.NewContainer()
app.AddChild(content) app.AddChild(content)
app.AddChild(overlays) app.AddChild(overlays)
content.AddChild(tins2020.NewTerrainRenderer(game.Terrain)) content.AddChild(tins2020.NewTerrainRenderer(game))
err = app.Init(ctx) err = app.Init(ctx)
if err != nil { if err != nil {
return err return err
@ -118,11 +125,6 @@ func run() error {
app.Arrange(ctx, tins2020.Rect(0, 0, w, h)) app.Arrange(ctx, tins2020.Rect(0, 0, w, h))
ctx.Settings.Window.Size = tins2020.PtPtr(w, h) ctx.Settings.Window.Size = tins2020.PtPtr(w, h)
} }
case *sdl.KeyboardEvent:
switch e.Keysym.Sym {
case sdl.K_ESCAPE:
ctx.Quit()
}
} }
app.Handle(ctx, event) app.Handle(ctx, event)
} }

View File

@ -9,11 +9,13 @@ type Control interface {
Render(*Context) Render(*Context)
} }
type EventFn func(*Context) type EventContextFn func(*Context)
type EmptyEventFn func() type EventFn func()
func EmptyEvent(fn EmptyEventFn) EventFn { type EventInterfaceFn func(interface{})
func EmptyEvent(fn EventFn) EventContextFn {
return func(*Context) { fn() } return func(*Context) { fn() }
} }
@ -22,7 +24,7 @@ type ControlBase struct {
IsMouseOver bool IsMouseOver bool
OnLeftMouseButtonClick EventFn OnLeftMouseButtonClick EventContextFn
} }
func (b *ControlBase) Arrange(ctx *Context, bounds Rectangle) { b.Bounds = bounds } func (b *ControlBase) Arrange(ctx *Context, bounds Rectangle) { b.Bounds = bounds }
@ -40,7 +42,7 @@ func (b *ControlBase) Handle(ctx *Context, event sdl.Event) {
} }
} }
func (b *ControlBase) Invoke(ctx *Context, fn EventFn) { func (b *ControlBase) Invoke(ctx *Context, fn EventContextFn) {
if fn == nil { if fn == nil {
return return
} }

33
eventhandler.go Normal file
View File

@ -0,0 +1,33 @@
package tins2020
func NewEvents() *Events {
return &Events{events: map[int]EventInterfaceFn{}}
}
type Events struct {
nextID int
events map[int]EventInterfaceFn
}
type EventHandler interface {
Register(EventFn) int
RegisterItf(EventInterfaceFn) int
Unregister(int)
}
func (h *Events) Notify(state interface{}) {
for _, event := range h.events {
event(state)
}
}
func (h *Events) Register(fn EventFn) int { return h.RegisterItf(func(interface{}) { fn() }) }
func (h *Events) RegisterItf(fn EventInterfaceFn) int {
id := h.nextID
h.nextID++
h.events[id] = fn
return id
}
func (h *Events) Unregister(id int) { delete(h.events, id) }

View File

@ -1,6 +1,7 @@
package tins2020 package tins2020
type Flower struct { type Flower struct {
ID string
Traits FlowerTraits Traits FlowerTraits
} }
@ -31,17 +32,68 @@ type FlowerResistance struct {
Wet float32 Wet float32
} }
// NewPoppyTraits creates the traits of a poppy, a very generic flower that thrives in a moderate climate. // NewAnemoneTraits creates the traits of a anemone, a very generic flower that thrives in a moderate climate.
func NewPoppyTraits() FlowerTraits { func NewAnemoneTraits() FlowerTraits {
return FlowerTraits{ return FlowerTraits{
// Spread: 0.0011, Spread: 0.0004,
Spread: 0.0011,
Life: 0.99993, Life: 0.99993,
Resistance: FlowerResistance{ Resistance: FlowerResistance{
Cold: 0.5, Cold: 0.7,
Hot: 0.5, Hot: 0.7,
Dry: 0.5, Dry: 0.5,
Wet: 0.5, Wet: 0.5,
}, },
} }
} }
func NewLoosestrifeTraits() FlowerTraits {
return FlowerTraits{
Spread: 0.0005,
Life: 0.99993,
Resistance: FlowerResistance{
Cold: 0.7,
Hot: 0.6,
Dry: 0.5,
Wet: 0.8,
},
}
}
func NewConeflowerTraits() FlowerTraits {
return FlowerTraits{
Spread: 0.0005,
Life: 0.99993,
Resistance: FlowerResistance{
Cold: 0.7,
Hot: 0.8,
Dry: 0.8,
Wet: 0.5,
},
}
}
func NewTulipTraits() FlowerTraits {
return FlowerTraits{
Spread: 0.0005,
Life: 0.99993,
Resistance: FlowerResistance{
Cold: 0.8,
Hot: 0.6,
Dry: 0.5,
Wet: 0.9,
},
}
}
func NewAjugaTraits() FlowerTraits {
return FlowerTraits{
Spread: 0.0007,
Life: 0.99991,
Resistance: FlowerResistance{
Cold: 0.9,
Hot: 0.5,
Dry: 0.75,
Wet: 0.75,
},
}
}

199
game.go
View File

@ -1,16 +1,22 @@
package tins2020 package tins2020
import ( import (
"log"
"math/rand" "math/rand"
"time" "time"
) )
type Game struct { type Game struct {
Balance int Balance int
Speed GameSpeed Speed GameSpeed
Terrain *Map SpeedBeforePause GameSpeed
Herbarium Herbarium
Terrain *Map
simulation Animation tool Tool
toolChanged *Events
speedChanged *Events
simulation Animation
} }
type GameSpeed string type GameSpeed string
@ -21,29 +27,6 @@ const (
GameSpeedPaused = "paused" GameSpeedPaused = "paused"
) )
type Map struct {
Temp NoiseMap
Humid NoiseMap
Variant NoiseMap
PlaceX NoiseMap // displacement map of props
PlaceY NoiseMap
Flowers map[Point]Flower
}
func (m *Map) AddFlower(pos Point, traits FlowerTraits) {
m.Flowers[pos] = m.NewFlower(pos, traits)
}
func (m *Map) NewFlower(pos Point, traits FlowerTraits) Flower {
flower := Flower{
Traits: traits,
}
temp, humid := float32(m.Temp.Value(pos.X, pos.Y)), float32(m.Humid.Value(pos.X, pos.Y))
flower.Traits.UpdateModifier(temp, humid)
return flower
}
const simulationInterval = 120 * time.Millisecond const simulationInterval = 120 * time.Millisecond
const fastSimulationInterval = 40 * time.Millisecond const fastSimulationInterval = 40 * time.Millisecond
@ -56,13 +39,88 @@ func NewGame() *Game {
PlaceY: NewRandomNoiseMap(rand.Int63()), PlaceY: NewRandomNoiseMap(rand.Int63()),
Flowers: map[Point]Flower{}, Flowers: map[Point]Flower{},
} }
terrain.AddFlower(Pt(0, 0), NewPoppyTraits()) herbarium := NewHerbarium()
herbarium.Add("anemone", FlowerDescriptor{
Name: "Anemone",
Description: "A very generic flower that thrives in a temperate climate.",
IconTemplate: "flower-anemone-%s",
BuyPrice: 10,
SellPrice: 3,
Traits: NewAnemoneTraits(),
Unlocked: true,
})
herbarium.Add("loosestrife", FlowerDescriptor{
Name: "Loosestrife",
Description: "A simple flower that will spread in temperate and wet climates.",
IconTemplate: "flower-loosestrife-%s",
BuyPrice: 100,
SellPrice: 20,
Traits: NewLoosestrifeTraits(),
Unlocked: false,
})
herbarium.Add("coneflower", FlowerDescriptor{
Name: "Coneflower",
Description: "A beautifull flower that can withstand hotter climates.",
IconTemplate: "flower-coneflower-%s",
BuyPrice: 500,
SellPrice: 100,
Traits: NewConeflowerTraits(),
Unlocked: false,
})
herbarium.Add("tulip", FlowerDescriptor{
Name: "Tulip",
Description: "A lovely flower that prefers a bit humid and colder climates.",
IconTemplate: "flower-tulip-%s",
BuyPrice: 20000,
SellPrice: 5000,
Traits: NewTulipTraits(),
Unlocked: false,
})
herbarium.Add("ajuga", FlowerDescriptor{
Name: "Ajuga",
Description: "A flower that is resitant to cold climates and spreads very easily.",
IconTemplate: "flower-ajuga-%s",
BuyPrice: 100000,
SellPrice: 10000,
Traits: NewAjugaTraits(),
Unlocked: false,
})
return &Game{ return &Game{
Speed: GameSpeedNormal, Speed: GameSpeedNormal,
Balance: 100, Balance: 100,
Terrain: terrain, Terrain: terrain,
Herbarium: herbarium,
simulation: NewAnimation(time.Millisecond * 10), speedChanged: NewEvents(),
toolChanged: NewEvents(),
simulation: NewAnimation(time.Millisecond * 10),
}
}
func (g *Game) selectTool(t Tool) {
g.tool = t
g.toolChanged.Notify(t)
}
func (g *Game) setSpeed(speed GameSpeed) {
if speed == g.Speed {
return
}
if speed == GameSpeedPaused {
g.SpeedBeforePause = g.Speed
}
g.Speed = speed
g.speedChanged.Notify(speed)
switch speed {
case GameSpeedPaused:
g.simulation.Pause()
case GameSpeedNormal:
g.simulation.SetInterval(simulationInterval)
g.simulation.Run()
case GameSpeedFast:
g.simulation.SetInterval(fastSimulationInterval)
g.simulation.Run()
} }
} }
@ -86,36 +144,87 @@ func (g *Game) tick() {
dst := randomNeighbor(pos) dst := randomNeighbor(pos)
if _, ok := g.Terrain.Flowers[dst]; !ok { if _, ok := g.Terrain.Flowers[dst]; !ok {
if _, ok := flowers[dst]; !ok { if _, ok := flowers[dst]; !ok {
flowers[dst] = g.Terrain.NewFlower(dst, flower.Traits) flowers[dst] = g.Terrain.NewFlower(dst, flower.ID, flower.Traits)
} }
} }
} }
if rand.Float32() < flower.Traits.Life*flower.Traits.LifeModifier { if Sqr32(rand.Float32()) < flower.Traits.Life*flower.Traits.LifeModifier {
flowers[pos] = flower flowers[pos] = flower
} }
} }
g.Terrain.Flowers = flowers g.Terrain.Flowers = flowers
} }
func (g *Game) Pause() { func (g *Game) CancelTool() {
g.Speed = GameSpeedPaused g.selectTool(nil)
g.simulation.Pause()
} }
func (g *Game) Run() { func (g *Game) Dig(tile Point) {
g.Speed = GameSpeedNormal id := g.Terrain.DigFlower(tile)
g.simulation.SetInterval(simulationInterval) desc, ok := g.Herbarium.Find(id)
g.simulation.Run() if !ok {
return
}
g.Balance += desc.SellPrice
} }
func (g *Game) RunFast() { func (g *Game) Pause() { g.setSpeed(GameSpeedPaused) }
g.Speed = GameSpeedFast
g.simulation.SetInterval(fastSimulationInterval) func (g *Game) PlantFlower(id string, tile Point) {
g.simulation.Run() flower, ok := g.Herbarium.Find(id)
if !ok {
log.Println("user was able to plant a flower that doesn't exist")
return
}
if flower.BuyPrice > g.Balance {
// TODO: notify user of insufficient balance?
return
}
g.Balance -= flower.BuyPrice
g.Terrain.AddFlower(tile, id, flower.Traits)
} }
func (g *Game) Resume() { g.setSpeed(g.SpeedBeforePause) }
func (g *Game) Run() { g.setSpeed(GameSpeedNormal) }
func (g *Game) RunFast() { g.setSpeed(GameSpeedFast) }
func (g *Game) SelectPlantFlowerTool(id string) {
g.selectTool(&PlantFlowerTool{FlowerID: id})
}
func (g *Game) SelectShovel() {
g.selectTool(&ShovelTool{})
}
func (g *Game) SelectResearch() {
g.Pause()
}
func (g *Game) SpeedChanged() EventHandler { return g.speedChanged }
func (g *Game) TogglePause() {
if g.Speed == GameSpeedPaused {
g.Resume()
} else {
g.Pause()
}
}
func (g *Game) Tool() Tool { return g.tool }
func (g *Game) ToolChanged() EventHandler { return g.toolChanged }
func (g *Game) Update() { func (g *Game) Update() {
for g.simulation.Animate() { for g.simulation.Animate() {
g.tick() g.tick()
} }
} }
func (g *Game) UserClickedTile(pos Point) {
if g.tool == nil {
return
}
g.tool.ClickedTile(g, pos)
}

View File

@ -1,70 +1,103 @@
package tins2020 package tins2020
import (
"github.com/veandco/go-sdl2/sdl"
)
type GameControls struct { type GameControls struct {
Container Container
game *Game game *Game
menu ButtonBar menu ButtonBar
top ButtonBar top ButtonBar
flowers ButtonBar flowers ButtonBar
otherTools ButtonBar
pause *IconButton pause *IconButton
run *IconButton run *IconButton
runFast *IconButton runFast *IconButton
shovel *IconButton
research *IconButton
} }
func NewGameControls(game *Game) *GameControls { func NewGameControls(game *Game) *GameControls {
return &GameControls{game: game} return &GameControls{game: game}
} }
func (c *GameControls) updateSpeedControls() { func (c *GameControls) createBuyFlowerButton(id string) *BuyFlowerButton {
disable := func(b *IconButton, speed GameSpeed) { flower, _ := c.game.Herbarium.Find(id)
b.IsDisabled = speed == c.game.Speed return NewBuyFlowerButton(
flower.IconTemplate.Variant(1),
flower.IconTemplate.Disabled(),
id,
flower,
EmptyEvent(func() {
c.game.SelectPlantFlowerTool(id)
}),
)
}
func (c *GameControls) speedChanged(state interface{}) {
speed := state.(GameSpeed)
disable := func(b *IconButton, expected GameSpeed) {
b.IsDisabled = speed == expected
} }
disable(c.pause, GameSpeedPaused) disable(c.pause, GameSpeedPaused)
disable(c.run, GameSpeedNormal) disable(c.run, GameSpeedNormal)
disable(c.runFast, GameSpeedFast) disable(c.runFast, GameSpeedFast)
} }
func (c *GameControls) toolChanged(state interface{}) {
tool, _ := state.(Tool)
var flowerID string
if tool, ok := tool.(*PlantFlowerTool); ok {
flowerID = tool.FlowerID
}
for _, control := range c.flowers.Buttons {
button := control.(*BuyFlowerButton)
button.IsActive = button.FlowerID == flowerID
}
_, shovel := tool.(*ShovelTool)
c.shovel.IsActive = shovel
}
func (c *GameControls) Arrange(ctx *Context, bounds Rectangle) { func (c *GameControls) Arrange(ctx *Context, bounds Rectangle) {
c.Bounds = bounds c.Bounds = bounds
c.menu.Arrange(ctx, RectSize(bounds.X, bounds.Y, buttonBarWidth, bounds.H)) c.menu.Arrange(ctx, RectSize(bounds.X, bounds.Y, buttonBarWidth, bounds.H))
c.top.Arrange(ctx, Rect(bounds.X+bounds.W/2+8, bounds.Y, bounds.Right(), bounds.Y+64)) c.top.Arrange(ctx, Rect(bounds.X+bounds.W/2+8, bounds.Y, bounds.Right(), bounds.Y+64))
c.flowers.Arrange(ctx, RectSize(bounds.Right()-buttonBarWidth, bounds.Y, buttonBarWidth, bounds.H)) c.flowers.Arrange(ctx, RectSize(bounds.Right()-buttonBarWidth, bounds.Y, buttonBarWidth, bounds.H))
} c.otherTools.Arrange(ctx, RectSize(bounds.Right()-buttonBarWidth, bounds.Bottom()-2*buttonBarWidth, buttonBarWidth, 2*buttonBarWidth))
func (c *GameControls) buyPoppy(ctx *Context) {
c.game.Balance -= 10
} }
func (c *GameControls) Init(ctx *Context) error { func (c *GameControls) Init(ctx *Context) error {
c.game.SpeedChanged().RegisterItf(c.speedChanged)
c.game.ToolChanged().RegisterItf(c.toolChanged)
c.flowers.Background = MustHexColor("#356dad") // brown alternative? #4ac69a c.flowers.Background = MustHexColor("#356dad") // brown alternative? #4ac69a
c.flowers.Buttons = []Control{
NewBuyFlowerButton("flower-poppy-1", "flower-poppy-disabled", "Poppy", 10, "A very generic flower that thrives in a moderate climate.", false, c.buyPoppy), for _, id := range c.game.Herbarium.Flowers() {
NewBuyFlowerButton("flower-red-c-1", "flower-poppy-disabled", "Unknown", 100, "Traits are not known yet.", true, nil), c.flowers.Buttons = append(c.flowers.Buttons, c.createBuyFlowerButton(id))
} }
c.top.Orientation = OrientationHorizontal c.top.Orientation = OrientationHorizontal
c.pause = NewIconButtonConfig("control-pause", EmptyEvent(func() { c.pause = NewIconButtonConfig("control-pause", EmptyEvent(func() {
c.game.Pause() c.game.Pause()
c.updateSpeedControls()
}), func(b *IconButton) { }), func(b *IconButton) {
b.IconDisabled = "control-pause-disabled" b.IconDisabled = "control-pause-disabled"
}) })
c.run = NewIconButtonConfig("control-run", EmptyEvent(func() { c.run = NewIconButtonConfig("control-run", EmptyEvent(func() {
c.game.Run() c.game.Run()
c.updateSpeedControls()
}), func(b *IconButton) { }), func(b *IconButton) {
b.IconDisabled = "control-run-disabled" b.IconDisabled = "control-run-disabled"
}) })
c.runFast = NewIconButtonConfig("control-run-fast", EmptyEvent(func() { c.runFast = NewIconButtonConfig("control-run-fast", EmptyEvent(func() {
c.game.RunFast() c.game.RunFast()
c.updateSpeedControls()
}), func(b *IconButton) { }), func(b *IconButton) {
b.IconDisabled = "control-run-fast-disabled" b.IconDisabled = "control-run-fast-disabled"
}) })
c.updateSpeedControls() c.speedChanged(c.game.Speed)
c.top.Buttons = []Control{c.pause, c.run, c.runFast} c.top.Buttons = []Control{c.pause, c.run, c.runFast}
c.menu.Background = MustHexColor("#356dad") c.menu.Background = MustHexColor("#356dad")
@ -74,12 +107,45 @@ func (c *GameControls) Init(ctx *Context) error {
NewIconButton("control-load", EmptyEvent(func() {})), NewIconButton("control-load", EmptyEvent(func() {})),
} }
c.shovel = NewIconButtonConfig("control-shovel", func(*Context) { c.game.SelectShovel() }, func(b *IconButton) { b.IconHeight = 48 })
c.research = NewIconButtonConfig("control-research", func(*Context) { c.game.SelectResearch() }, func(b *IconButton) { b.IconHeight = 48 })
c.otherTools.Buttons = []Control{c.shovel, c.research}
c.Container.AddChild(&c.menu) c.Container.AddChild(&c.menu)
c.Container.AddChild(&c.top) c.Container.AddChild(&c.top)
c.Container.AddChild(&c.flowers) c.Container.AddChild(&c.flowers)
c.Container.AddChild(&c.otherTools)
return c.Container.Init(ctx) return c.Container.Init(ctx)
} }
func (c *GameControls) Handle(ctx *Context, event sdl.Event) {
c.Container.Handle(ctx, event)
switch e := event.(type) {
case *sdl.KeyboardEvent:
if e.Type == sdl.KEYDOWN {
switch e.Keysym.Sym {
case sdl.K_SPACE:
c.game.TogglePause()
case sdl.K_1:
c.game.Run()
case sdl.K_2:
c.game.RunFast()
case sdl.K_d:
c.game.SelectShovel()
case sdl.K_r:
c.game.SelectResearch()
case sdl.K_ESCAPE:
if c.game.Tool() == nil {
// TODO: display menu
} else {
c.game.CancelTool()
}
}
}
}
}
func (c *GameControls) Render(ctx *Context) { func (c *GameControls) Render(ctx *Context) {
topBar := MustHexColor("#0000007f") topBar := MustHexColor("#0000007f")
ctx.Renderer.SetDrawColor(topBar.R, topBar.G, topBar.B, topBar.A) ctx.Renderer.SetDrawColor(topBar.R, topBar.G, topBar.B, topBar.A)

46
herbarium.go Normal file
View File

@ -0,0 +1,46 @@
package tins2020
import (
"fmt"
"strconv"
)
type Herbarium struct {
flowers map[string]FlowerDescriptor
order []string
}
func NewHerbarium() Herbarium {
return Herbarium{map[string]FlowerDescriptor{}, nil}
}
type FlowerDescriptor struct {
Name string
Description string
BuyPrice int
SellPrice int
Unlocked bool
IconTemplate IconTemplate
Traits FlowerTraits
}
type IconTemplate string
func (t IconTemplate) Disabled() string { return t.Fmt("disabled") }
func (t IconTemplate) Fmt(s string) string { return fmt.Sprintf(string(t), s) }
func (t IconTemplate) Variant(i int) string { return t.Fmt(strconv.Itoa(i)) }
func (h *Herbarium) Add(id string, desc FlowerDescriptor) {
h.flowers[id] = desc
h.order = append(h.order, id)
}
func (h *Herbarium) Find(id string) (FlowerDescriptor, bool) {
flower, ok := h.flowers[id]
return flower, ok
}
func (h *Herbarium) Flowers() []string { return h.order }

View File

@ -5,9 +5,11 @@ type IconButton struct {
Icon string Icon string
IconDisabled string IconDisabled string
IconHeight int32
IconScale Scale IconScale Scale
IconWidth int32 IconWidth int32
IsActive bool
IsDisabled bool IsDisabled bool
} }
@ -18,7 +20,7 @@ const (
ScaleStretch ScaleStretch
) )
func NewIconButton(icon string, onClick EventFn) *IconButton { func NewIconButton(icon string, onClick EventContextFn) *IconButton {
return &IconButton{ return &IconButton{
ControlBase: ControlBase{ ControlBase: ControlBase{
OnLeftMouseButtonClick: onClick, OnLeftMouseButtonClick: onClick,
@ -27,7 +29,7 @@ func NewIconButton(icon string, onClick EventFn) *IconButton {
} }
} }
func NewIconButtonConfig(icon string, onClick EventFn, configure func(*IconButton)) *IconButton { func NewIconButtonConfig(icon string, onClick EventContextFn, configure func(*IconButton)) *IconButton {
button := NewIconButton(icon, onClick) button := NewIconButton(icon, onClick)
configure(button) configure(button)
return button return button
@ -51,12 +53,14 @@ func (b *IconButton) Render(ctx *Context) {
size := iconTexture.Size() size := iconTexture.Size()
if b.IconWidth != 0 { if b.IconWidth != 0 {
size = Pt(b.IconWidth, b.IconWidth*size.Y/size.X) size = Pt(b.IconWidth, b.IconWidth*size.Y/size.X)
} else if b.IconHeight != 0 {
size = Pt(b.IconHeight*size.X/size.Y, b.IconHeight)
} }
iconTexture.CopyResize(ctx.Renderer, RectSize(b.Bounds.X+(b.Bounds.W-size.X)/2, b.Bounds.Y+(b.Bounds.H-size.Y)/2, size.X, size.Y)) iconTexture.CopyResize(ctx.Renderer, RectSize(b.Bounds.X+(b.Bounds.W-size.X)/2, b.Bounds.Y+(b.Bounds.H-size.Y)/2, size.X, size.Y))
} else { } else {
iconTexture.CopyResize(ctx.Renderer, b.Bounds) iconTexture.CopyResize(ctx.Renderer, b.Bounds)
} }
if b.IsMouseOver && !b.IsDisabled { if (b.IsMouseOver && !b.IsDisabled) || b.IsActive {
mouseOverTexture.CopyResize(ctx.Renderer, b.Bounds) mouseOverTexture.CopyResize(ctx.Renderer, b.Bounds)
} }
} }

34
map.go Normal file
View File

@ -0,0 +1,34 @@
package tins2020
type Map struct {
Temp NoiseMap
Humid NoiseMap
Variant NoiseMap
PlaceX NoiseMap // displacement map of props
PlaceY NoiseMap
Flowers map[Point]Flower
}
func (m *Map) AddFlower(pos Point, id string, traits FlowerTraits) {
m.Flowers[pos] = m.NewFlower(pos, id, traits)
}
func (m *Map) DigFlower(pos Point) string {
flower, ok := m.Flowers[pos]
if !ok {
return ""
}
delete(m.Flowers, pos)
return flower.ID
}
func (m *Map) NewFlower(pos Point, id string, traits FlowerTraits) Flower {
flower := Flower{
ID: id,
Traits: traits,
}
temp, humid := float32(m.Temp.Value(pos.X, pos.Y)), float32(m.Humid.Value(pos.X, pos.Y))
flower.Traits.UpdateModifier(temp, humid)
return flower
}

View File

@ -50,4 +50,6 @@ func Min32(a, b float32) float32 {
func Round32(x float32) float32 { return float32(math.Round(float64(x))) } func Round32(x float32) float32 { return float32(math.Round(float64(x))) }
func Sqr32(x float32) float32 { return x * x }
func Sqrt32(x float32) float32 { return float32(math.Sqrt(float64(x))) } func Sqrt32(x float32) float32 { return float32(math.Sqrt(float64(x))) }

View File

@ -10,6 +10,8 @@ func (p Point) Add(q Point) Point { return Pt(p.X+q.X, p.Y+q.Y) }
func (p Point) In(r Rectangle) bool { return r.IsPointInsidePt(p) } func (p Point) In(r Rectangle) bool { return r.IsPointInsidePt(p) }
func (p Point) Sub(q Point) Point { return Pt(p.X-q.X, p.Y-q.Y) }
func (p Point) ToPtF() PointF { return PtF(float32(p.X), float32(p.Y)) } func (p Point) ToPtF() PointF { return PtF(float32(p.X), float32(p.Y)) }
type PointF struct { type PointF struct {

View File

@ -39,6 +39,11 @@ func (p *projection) screenToMap(x, y int32) PointF {
return p.center.Add(pos) return p.center.Add(pos)
} }
func (p *projection) screenToMapInt(x, y int32) Point {
pos := p.screenToMap(x, y)
return Pt(int32(Round32(pos.X)), int32(Round32(pos.Y)))
}
func (p *projection) screenToMapRel(x, y int32) PointF { func (p *projection) screenToMapRel(x, y int32) PointF {
normX := p.zoomInv * float32(x) normX := p.zoomInv * float32(x)
normY := p.zoomInv * float32(y) normY := p.zoomInv * float32(y)

View File

@ -32,4 +32,5 @@ func (s *Settings) Store() error {
type WindowSettings struct { type WindowSettings struct {
Location *Point Location *Point
Size *Point Size *Point
VSync *bool
} }

View File

@ -7,21 +7,39 @@ import (
) )
type terrainRenderer struct { type terrainRenderer struct {
game *Game
terrain *Map terrain *Map
hover *Point hover *Point
project projection project projection
interact interaction drag Drageable
} }
type interaction struct { type Drageable struct {
mousePos Point start *Point
mouseLeftDown bool dragged bool
mouseDrag *Point
} }
func NewTerrainRenderer(terrain *Map) Control { func (d *Drageable) Cancel() { d.start = nil }
return &terrainRenderer{terrain: terrain, project: newProjection()}
func (d *Drageable) IsDragging() bool { return d.start != nil }
func (d *Drageable) HasDragged() bool { return d.dragged }
func (d *Drageable) Move(p Point) Point {
delta := p.Sub(*d.start)
d.start = &p
d.dragged = true
return delta
}
func (d *Drageable) Start(p Point) {
d.start = &p
d.dragged = false
}
func NewTerrainRenderer(game *Game) Control {
return &terrainRenderer{game: game, terrain: game.Terrain, project: newProjection()}
} }
func (r *terrainRenderer) Arrange(ctx *Context, _ Rectangle) { func (r *terrainRenderer) Arrange(ctx *Context, _ Rectangle) {
@ -33,30 +51,48 @@ func (r *terrainRenderer) Init(ctx *Context) error {
return nil return nil
} }
func isControlKeyDown() bool {
state := sdl.GetKeyboardState()
return state[sdl.SCANCODE_LCTRL] == 1 || state[sdl.SCANCODE_RCTRL] == 1
}
func (r *terrainRenderer) Handle(ctx *Context, event sdl.Event) { func (r *terrainRenderer) Handle(ctx *Context, event sdl.Event) {
switch e := event.(type) { switch e := event.(type) {
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.Button == sdl.BUTTON_LEFT { if e.Type == sdl.MOUSEBUTTONDOWN {
r.interact.mouseLeftDown = e.Type == sdl.MOUSEBUTTONDOWN if e.Button == sdl.BUTTON_MIDDLE || (e.Button == sdl.BUTTON_LEFT && isControlKeyDown()) {
if r.interact.mouseLeftDown && r.interact.mouseDrag == nil { if !r.drag.IsDragging() {
r.interact.mouseDrag = PtPtr(e.X, e.Y) r.drag.Start(Pt(e.X, e.Y))
} else if !r.interact.mouseLeftDown && r.interact.mouseDrag != nil { }
r.interact.mouseDrag = nil }
if e.Button == sdl.BUTTON_LEFT {
pos := r.project.screenToMapInt(e.X, e.Y)
r.game.UserClickedTile(pos)
}
if e.Button == sdl.BUTTON_RIGHT {
if e.Type == sdl.MOUSEBUTTONDOWN {
r.game.CancelTool()
}
}
}
if e.Type == sdl.MOUSEBUTTONUP {
if r.drag.IsDragging() {
r.drag.Cancel()
} }
} }
} }
case *sdl.MouseMotionEvent: case *sdl.MouseMotionEvent:
if r.project.windowInteractRect.IsPointInside(e.X, e.Y) { if r.project.windowInteractRect.IsPointInside(e.X, e.Y) {
hover := r.project.screenToMap(e.X, e.Y) hover := r.project.screenToMapInt(e.X, e.Y)
r.hover = PtPtr(int32(Round32(hover.X)), int32(Round32(hover.Y))) r.hover = &hover
} else { } else {
r.hover = nil r.hover = nil
} }
if r.interact.mouseDrag != nil { if r.drag.IsDragging() {
r.project.center = r.project.center.Sub(r.project.screenToMapRel(e.X-r.interact.mouseDrag.X, e.Y-r.interact.mouseDrag.Y)) delta := r.drag.Move(Pt(e.X, e.Y))
r.project.center = r.project.center.Sub(r.project.screenToMapRel(delta.X, delta.Y))
r.project.update(ctx.Renderer) r.project.update(ctx.Renderer)
r.interact.mouseDrag = PtPtr(e.X, e.Y)
} }
case *sdl.MouseWheelEvent: case *sdl.MouseWheelEvent:
if r.hover != nil { if r.hover != nil {
@ -143,9 +179,10 @@ func (r *terrainRenderer) Render(ctx *Context) {
toItemTexture := func(x, y int32) *Texture { toItemTexture := func(x, y int32) *Texture {
variant := r.terrain.Variant.Value(x, y) variant := r.terrain.Variant.Value(x, y)
_, ok := r.terrain.Flowers[Pt(x, y)] flower, ok := r.terrain.Flowers[Pt(x, y)]
if ok { if ok {
return variantToTexture("flower-poppy-%d", variant) desc, _ := r.game.Herbarium.Find(flower.ID)
return ctx.Textures.Texture(desc.IconTemplate.Variant(variantToInt(variant)))
} }
temp := r.terrain.Temp.Value(x, y) temp := r.terrain.Temp.Value(x, y)
humid := r.terrain.Humid.Value(x, y) humid := r.terrain.Humid.Value(x, y)

24
tools.go Normal file
View File

@ -0,0 +1,24 @@
package tins2020
type Tool interface {
Type() string
ClickedTile(*Game, Point)
}
type PlantFlowerTool struct {
FlowerID string
}
func (t *PlantFlowerTool) Type() string { return "plant-flower" }
func (t *PlantFlowerTool) ClickedTile(game *Game, tile Point) {
game.PlantFlower(t.FlowerID, tile)
}
type ShovelTool struct{}
func (t *ShovelTool) Type() string { return "shovel" }
func (t *ShovelTool) ClickedTile(game *Game, tile Point) {
game.Dig(tile)
}