Compare commits
5 Commits
27afe594fe
...
2413c7d3d4
Author | SHA1 | Date | |
---|---|---|---|
2413c7d3d4 | |||
b32017f18a | |||
0a73529306 | |||
58aa819a4e | |||
8dd47c5ec2 |
BIN
cmd/tins2020/res/images/flower_purpleA_NE.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
cmd/tins2020/res/images/flower_purpleA_NW.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
cmd/tins2020/res/images/flower_purpleA_SE.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
cmd/tins2020/res/images/flower_purpleA_SW.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
cmd/tins2020/res/images/flower_purpleC_NE.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
cmd/tins2020/res/images/flower_purpleC_NW.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
cmd/tins2020/res/images/flower_purpleC_SE.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
cmd/tins2020/res/images/flower_purpleC_SW.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
cmd/tins2020/res/images/flower_redA_NE.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
cmd/tins2020/res/images/flower_redA_NW.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
cmd/tins2020/res/images/flower_redA_SE.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
cmd/tins2020/res/images/flower_redA_SW.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
cmd/tins2020/res/images/flower_redC_NE.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
cmd/tins2020/res/images/flower_redC_NW.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
cmd/tins2020/res/images/flower_redC_SE.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
cmd/tins2020/res/images/flower_redC_SW.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
cmd/tins2020/res/images/flower_yellowC_NE.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
cmd/tins2020/res/images/flower_yellowC_NW.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
cmd/tins2020/res/images/flower_yellowC_SE.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
cmd/tins2020/res/images/flower_yellowC_SW.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
74
cmd/tins2020/res/textures.txt
Normal file
@ -0,0 +1,74 @@
|
||||
|
||||
tile-dirt: images/tile_dirt.png
|
||||
tile-grass: images/tile_grass.png
|
||||
tile-snow: images/tile_snow.png
|
||||
|
||||
cactus-small-1: images/cactus_short_NE.png
|
||||
cactus-small-2: images/cactus_short_NW.png
|
||||
cactus-small-3: images/cactus_short_SW.png
|
||||
cactus-small-4: images/cactus_short_SE.png
|
||||
|
||||
cactus-tall-1: images/cactus_tall_NE.png
|
||||
cactus-tall-2: images/cactus_tall_NW.png
|
||||
cactus-tall-3: images/cactus_tall_SW.png
|
||||
cactus-tall-4: images/cactus_tall_SE.png
|
||||
|
||||
tree-fat-1: images/tree_fat_NE.png
|
||||
tree-fat-2: images/tree_fat_NW.png
|
||||
tree-fat-3: images/tree_fat_SW.png
|
||||
tree-fat-4: images/tree_fat_SE.png
|
||||
|
||||
tree-pine-1: images/tree_pineDefaultA_NE.png
|
||||
tree-pine-2: images/tree_pineDefaultA_NW.png
|
||||
tree-pine-3: images/tree_pineDefaultA_SW.png
|
||||
tree-pine-4: images/tree_pineDefaultA_SE.png
|
||||
|
||||
grass-small-1: images/grass_NE.png
|
||||
grass-small-2: images/grass_NW.png
|
||||
grass-small-3: images/grass_SW.png
|
||||
grass-small-4: images/grass_SE.png
|
||||
|
||||
grass-leafs-1: images/grass_leafs_NE.png
|
||||
grass-leafs-2: images/grass_leafs_NW.png
|
||||
grass-leafs-3: images/grass_leafs_SW.png
|
||||
grass-leafs-4: images/grass_leafs_SE.png
|
||||
|
||||
bush-small-1: images/plant_bushSmall_NE.png
|
||||
bush-small-2: images/plant_bushSmall_NW.png
|
||||
bush-small-3: images/plant_bushSmall_SW.png
|
||||
bush-small-4: images/plant_bushSmall_SE.png
|
||||
|
||||
bush-large-1: images/plant_bushLarge_NE.png
|
||||
bush-large-2: images/plant_bushLarge_NW.png
|
||||
bush-large-3: images/plant_bushLarge_SW.png
|
||||
bush-large-4: images/plant_bushLarge_SE.png
|
||||
|
||||
bush-large-1: images/plant_bushLarge_NE.png
|
||||
bush-large-2: images/plant_bushLarge_NW.png
|
||||
bush-large-3: images/plant_bushLarge_SW.png
|
||||
bush-large-4: images/plant_bushLarge_SE.png
|
||||
|
||||
flower-poppy-1: images/flower_yellowC_NE.png
|
||||
flower-poppy-2: images/flower_yellowC_NW.png
|
||||
flower-poppy-3: images/flower_yellowC_SW.png
|
||||
flower-poppy-4: images/flower_yellowC_SE.png
|
||||
|
||||
flower-red-c-1: images/flower_redC_NE.png
|
||||
flower-red-c-2: images/flower_redC_NW.png
|
||||
flower-red-c-3: images/flower_redC_SW.png
|
||||
flower-red-c-4: images/flower_redC_SE.png
|
||||
|
||||
flower-red-a-1: images/flower_redA_NE.png
|
||||
flower-red-a-2: images/flower_redA_NW.png
|
||||
flower-red-a-3: images/flower_redA_SW.png
|
||||
flower-red-a-4: images/flower_redA_SE.png
|
||||
|
||||
flower-purple-a-1: images/flower_purpleA_NE.png
|
||||
flower-purple-a-2: images/flower_purpleA_NW.png
|
||||
flower-purple-a-3: images/flower_purpleA_SW.png
|
||||
flower-purple-a-4: images/flower_purpleA_SE.png
|
||||
|
||||
flower-purple-c-1: images/flower_purpleC_NE.png
|
||||
flower-purple-c-2: images/flower_purpleC_NW.png
|
||||
flower-purple-c-3: images/flower_purpleC_SW.png
|
||||
flower-purple-c-4: images/flower_purpleC_SE.png
|
@ -47,10 +47,18 @@ func run() error {
|
||||
Y: sdl.WINDOWPOS_UNDEFINED,
|
||||
}
|
||||
}
|
||||
if ctx.Settings.Window.Size == nil {
|
||||
ctx.Settings.Window.Size = &tins2020.Point{
|
||||
X: 800,
|
||||
Y: 600,
|
||||
}
|
||||
}
|
||||
|
||||
sdl.SetHint(sdl.HINT_RENDER_SCALE_QUALITY, "1")
|
||||
window, err := sdl.CreateWindow("TINS 2020", ctx.Settings.Window.Location.X, ctx.Settings.Window.Location.Y,
|
||||
800, 600, sdl.WINDOW_SHOWN|sdl.WINDOW_RESIZABLE)
|
||||
window, err := sdl.CreateWindow("TINS 2020",
|
||||
ctx.Settings.Window.Location.X, ctx.Settings.Window.Location.Y,
|
||||
ctx.Settings.Window.Size.X, ctx.Settings.Window.Size.Y,
|
||||
sdl.WINDOW_SHOWN|sdl.WINDOW_RESIZABLE)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -64,62 +72,31 @@ func run() error {
|
||||
|
||||
ctx.Init(renderer)
|
||||
|
||||
err = ctx.Fonts.Load("default", "fonts/OpenSans-Regular.ttf", 12)
|
||||
err = ctx.Fonts.LoadDesc(
|
||||
tins2020.FontDescriptor{Name: "debug", Path: "fonts/OpenSans-Regular.ttf", Size: 12},
|
||||
tins2020.FontDescriptor{Name: "default", Path: "fonts/FiraMono-Regular.ttf", Size: 10},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ctx.Textures.Load(
|
||||
"tile-dirt", "images/tile_dirt.png",
|
||||
"tile-grass", "images/tile_grass.png",
|
||||
"tile-snow", "images/tile_snow.png",
|
||||
|
||||
"cactus-small-1", "images/cactus_short_NE.png",
|
||||
"cactus-small-2", "images/cactus_short_NW.png",
|
||||
"cactus-small-3", "images/cactus_short_SW.png",
|
||||
"cactus-small-4", "images/cactus_short_SE.png",
|
||||
|
||||
"cactus-tall-1", "images/cactus_tall_NE.png",
|
||||
"cactus-tall-2", "images/cactus_tall_NW.png",
|
||||
"cactus-tall-3", "images/cactus_tall_SW.png",
|
||||
"cactus-tall-4", "images/cactus_tall_SE.png",
|
||||
|
||||
"tree-fat-1", "images/tree_fat_NE.png",
|
||||
"tree-fat-2", "images/tree_fat_NW.png",
|
||||
"tree-fat-3", "images/tree_fat_SW.png",
|
||||
"tree-fat-4", "images/tree_fat_SE.png",
|
||||
|
||||
"tree-pine-1", "images/tree_pineDefaultA_NE.png",
|
||||
"tree-pine-2", "images/tree_pineDefaultA_NW.png",
|
||||
"tree-pine-3", "images/tree_pineDefaultA_SW.png",
|
||||
"tree-pine-4", "images/tree_pineDefaultA_SE.png",
|
||||
|
||||
"grass-small-1", "images/grass_NE.png",
|
||||
"grass-small-2", "images/grass_NW.png",
|
||||
"grass-small-3", "images/grass_SW.png",
|
||||
"grass-small-4", "images/grass_SE.png",
|
||||
|
||||
"grass-leafs-1", "images/grass_leafs_NE.png",
|
||||
"grass-leafs-2", "images/grass_leafs_NW.png",
|
||||
"grass-leafs-3", "images/grass_leafs_SW.png",
|
||||
"grass-leafs-4", "images/grass_leafs_SE.png",
|
||||
|
||||
"bush-small-1", "images/plant_bushSmall_NE.png",
|
||||
"bush-small-2", "images/plant_bushSmall_NW.png",
|
||||
"bush-small-3", "images/plant_bushSmall_SW.png",
|
||||
"bush-small-4", "images/plant_bushSmall_SE.png",
|
||||
|
||||
"bush-large-1", "images/plant_bushLarge_NE.png",
|
||||
"bush-large-2", "images/plant_bushLarge_NW.png",
|
||||
"bush-large-3", "images/plant_bushLarge_SW.png",
|
||||
"bush-large-4", "images/plant_bushLarge_SE.png",
|
||||
)
|
||||
textureLoader := tins2020.NewResourceLoader()
|
||||
err = textureLoader.LoadFromFile(&ctx.Resources, "textures.txt", func(name, content string) error {
|
||||
return ctx.Textures.Load(name, content)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
game := tins2020.NewGame()
|
||||
control := tins2020.NewTerrainRenderer(game.Terrain)
|
||||
err = control.Init(ctx)
|
||||
|
||||
app := tins2020.NewContainer()
|
||||
overlays := tins2020.NewContainer()
|
||||
overlays.AddChild(&tins2020.FPS{})
|
||||
content := tins2020.NewContainer()
|
||||
app.AddChild(content)
|
||||
app.AddChild(overlays)
|
||||
content.AddChild(tins2020.NewTerrainRenderer(game.Terrain))
|
||||
err = app.Init(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -135,6 +112,9 @@ func run() error {
|
||||
case sdl.WINDOWEVENT_MOVED:
|
||||
x, y := window.GetPosition()
|
||||
ctx.Settings.Window.Location = &tins2020.Point{X: x, Y: y}
|
||||
case sdl.WINDOWEVENT_SIZE_CHANGED:
|
||||
w, h := window.GetSize()
|
||||
ctx.Settings.Window.Size = &tins2020.Point{X: w, Y: h}
|
||||
}
|
||||
case *sdl.KeyboardEvent:
|
||||
switch e.Keysym.Sym {
|
||||
@ -142,8 +122,9 @@ func run() error {
|
||||
ctx.Quit()
|
||||
}
|
||||
}
|
||||
control.Handle(ctx, event)
|
||||
app.Handle(ctx, event)
|
||||
}
|
||||
game.Update()
|
||||
|
||||
if ctx.ShouldQuit {
|
||||
break
|
||||
@ -151,7 +132,7 @@ func run() error {
|
||||
|
||||
renderer.SetDrawColor(0, 0, 0, 255)
|
||||
renderer.Clear()
|
||||
control.Render(ctx)
|
||||
app.Render(ctx)
|
||||
renderer.Present()
|
||||
}
|
||||
return nil
|
||||
|
39
container.go
Normal file
@ -0,0 +1,39 @@
|
||||
package tins2020
|
||||
|
||||
import (
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
type Container struct {
|
||||
Children []Control
|
||||
}
|
||||
|
||||
func NewContainer() *Container {
|
||||
return &Container{}
|
||||
}
|
||||
|
||||
func (c *Container) AddChild(child Control) {
|
||||
c.Children = append(c.Children, child)
|
||||
}
|
||||
|
||||
func (c *Container) Handle(ctx *Context, event sdl.Event) {
|
||||
for _, child := range c.Children {
|
||||
child.Handle(ctx, event)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Container) Render(ctx *Context) {
|
||||
for _, child := range c.Children {
|
||||
child.Render(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Container) Init(ctx *Context) error {
|
||||
for _, child := range c.Children {
|
||||
err := child.Init(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
18
control.go
Normal file
@ -0,0 +1,18 @@
|
||||
package tins2020
|
||||
|
||||
import "github.com/veandco/go-sdl2/sdl"
|
||||
|
||||
type Control interface {
|
||||
Init(*Context) error
|
||||
Handle(*Context, sdl.Event)
|
||||
Render(*Context)
|
||||
}
|
||||
|
||||
type ControlBase struct {
|
||||
}
|
||||
|
||||
func (b *ControlBase) Handle(*Context, sdl.Event) {}
|
||||
|
||||
func (b *ControlBase) Render(*Context) {}
|
||||
|
||||
func (b *ControlBase) Init(*Context) error { return nil }
|
46
flower.go
Normal file
@ -0,0 +1,46 @@
|
||||
package tins2020
|
||||
|
||||
type Flower struct {
|
||||
Traits FlowerTraits
|
||||
}
|
||||
|
||||
type FlowerTraits struct {
|
||||
Spread float32
|
||||
Life float32
|
||||
LifeModifier float32
|
||||
Resistance FlowerResistance
|
||||
}
|
||||
|
||||
func (t *FlowerTraits) UpdateModifier(temp, humid float32) {
|
||||
mod := float32(1)
|
||||
cold := temp * 2
|
||||
mod *= Min32(1, Sqrt32(t.Resistance.Cold*t.Resistance.Cold+cold*cold))
|
||||
hot := (1 - temp) * 2
|
||||
mod *= Min32(1, Sqrt32(t.Resistance.Hot*t.Resistance.Hot+hot*hot))
|
||||
dry := humid * 2
|
||||
mod *= Min32(1, Sqrt32(t.Resistance.Dry*t.Resistance.Dry+dry*dry))
|
||||
wet := (1 - humid) * 2
|
||||
mod *= Min32(1, Sqrt32(t.Resistance.Wet*t.Resistance.Wet+wet*wet))
|
||||
t.LifeModifier = mod
|
||||
}
|
||||
|
||||
type FlowerResistance struct {
|
||||
Cold float32 // 0 will die almost instantly, 1 is completely resistant.
|
||||
Hot float32
|
||||
Dry float32
|
||||
Wet float32
|
||||
}
|
||||
|
||||
// NewPoppyTraits creates the traits of a poppy, a very generic flower that thrives in a moderate climate.
|
||||
func NewPoppyTraits() FlowerTraits {
|
||||
return FlowerTraits{
|
||||
Spread: 0.01,
|
||||
Life: 0.9991,
|
||||
Resistance: FlowerResistance{
|
||||
Cold: 0.3,
|
||||
Hot: 0.3,
|
||||
Dry: 0.3,
|
||||
Wet: 0.3,
|
||||
},
|
||||
}
|
||||
}
|
54
fonts.go
@ -1,18 +1,66 @@
|
||||
package tins2020
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
"github.com/veandco/go-sdl2/ttf"
|
||||
"opslag.de/schobers/fs/vfs"
|
||||
)
|
||||
|
||||
type Font struct {
|
||||
*ttf.Font
|
||||
}
|
||||
|
||||
func (f *Font) Render(renderer *sdl.Renderer, text string, pos Point, color sdl.Color) (*Texture, error) {
|
||||
surface, err := f.RenderUTF8Solid(text, color)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer surface.Free()
|
||||
texture, err := NewTextureFromSurface(renderer, surface)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return texture, nil
|
||||
}
|
||||
|
||||
func (f *Font) RenderCopy(renderer *sdl.Renderer, text string, pos Point, color sdl.Color) error {
|
||||
texture, err := f.Render(renderer, text, pos, color)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer texture.Destroy()
|
||||
texture.Copy(renderer, texture.RectOffset(pos))
|
||||
return nil
|
||||
}
|
||||
|
||||
type Fonts struct {
|
||||
dir vfs.CopyDir
|
||||
fonts map[string]*ttf.Font
|
||||
fonts map[string]*Font
|
||||
}
|
||||
|
||||
func (f *Fonts) Init(dir vfs.CopyDir) {
|
||||
f.dir = dir
|
||||
f.fonts = map[string]*ttf.Font{}
|
||||
f.fonts = map[string]*Font{}
|
||||
}
|
||||
|
||||
func (f *Fonts) Font(name string) *Font { return f.fonts[name] }
|
||||
|
||||
type FontDescriptor struct {
|
||||
Name string
|
||||
Path string
|
||||
Size int
|
||||
}
|
||||
|
||||
func (f *Fonts) LoadDesc(fonts ...FontDescriptor) error {
|
||||
for _, desc := range fonts {
|
||||
err := f.Load(desc.Name, desc.Path, desc.Size)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error loading '%s'; error: %v", desc.Name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Fonts) Load(name, path string, size int) error {
|
||||
@ -27,7 +75,7 @@ func (f *Fonts) Load(name, path string, size int) error {
|
||||
if font, ok := f.fonts[name]; ok {
|
||||
font.Close()
|
||||
}
|
||||
f.fonts[name] = font
|
||||
f.fonts[name] = &Font{font}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
41
fpsrenderer.go
Normal file
@ -0,0 +1,41 @@
|
||||
package tins2020
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
type FPS struct {
|
||||
ControlBase
|
||||
|
||||
start time.Time
|
||||
stamp time.Duration
|
||||
slot int
|
||||
ticks []int
|
||||
total int
|
||||
}
|
||||
|
||||
func (f *FPS) Init(*Context) error {
|
||||
f.start = time.Now()
|
||||
f.stamp = 0
|
||||
f.ticks = make([]int, 51)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FPS) Render(ctx *Context) {
|
||||
elapsed := time.Since(f.start)
|
||||
stamp := elapsed / (20 * time.Millisecond)
|
||||
for f.stamp < stamp {
|
||||
f.total += f.ticks[f.slot]
|
||||
f.slot = (f.slot + 1) % len(f.ticks)
|
||||
f.total -= f.ticks[f.slot]
|
||||
f.ticks[f.slot] = 0
|
||||
f.stamp++
|
||||
}
|
||||
f.ticks[f.slot]++
|
||||
|
||||
font := ctx.Fonts.Font("debug")
|
||||
font.RenderCopy(ctx.Renderer, fmt.Sprintf("FPS: %d", f.total), Pt(5, 5), sdl.Color{R: 255, G: 255, B: 255, A: 255})
|
||||
}
|
109
game.go
@ -2,14 +2,15 @@ package tins2020
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
|
||||
"opslag.de/schobers/geom/noise"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Game struct {
|
||||
Money int
|
||||
Terrain *Map
|
||||
Flowers []Flower
|
||||
Bees []Bee
|
||||
|
||||
start time.Time
|
||||
lastUpdate time.Duration
|
||||
}
|
||||
|
||||
type Map struct {
|
||||
@ -18,55 +19,21 @@ type Map struct {
|
||||
Variant NoiseMap
|
||||
PlaceX NoiseMap // displacement map of props
|
||||
PlaceY NoiseMap
|
||||
|
||||
Flowers map[Point]Flower
|
||||
}
|
||||
|
||||
type NoiseMap interface {
|
||||
Value(x, y int32) float64
|
||||
func (m *Map) AddFlower(pos Point, traits FlowerTraits) {
|
||||
m.Flowers[pos] = m.NewFlower(pos, traits)
|
||||
}
|
||||
|
||||
func NewNoiseMap(seed int64) NoiseMap {
|
||||
return &noiseMap{
|
||||
noise: noise.NewPerlin(seed),
|
||||
alpha: 2,
|
||||
beta: 4,
|
||||
harmonics: 2,
|
||||
func (m *Map) NewFlower(pos Point, traits FlowerTraits) Flower {
|
||||
flower := Flower{
|
||||
Traits: traits,
|
||||
}
|
||||
}
|
||||
|
||||
func NewRandomNoiseMap(seed int64) NoiseMap {
|
||||
return &randomNoiseMap{noise.NewPerlin(seed)}
|
||||
}
|
||||
|
||||
type noiseMap struct {
|
||||
noise *noise.Perlin
|
||||
alpha, beta float64
|
||||
harmonics int
|
||||
}
|
||||
|
||||
func clipNormalized(x float64) float64 {
|
||||
if x < 0 {
|
||||
return 0
|
||||
}
|
||||
if x > 1 {
|
||||
return 1
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// Value generates the noise value for an x/y pair. The range of the output is approximately [-1.325825214,1.325825214] (for 4 harmonics).
|
||||
func (m *noiseMap) Value(x, y int32) float64 {
|
||||
value := m.noise.Noise2D(float64(x)*.01, float64(y)*.01, m.alpha, m.beta, m.harmonics)*.565 + .5
|
||||
return clipNormalized(value)
|
||||
}
|
||||
|
||||
type randomNoiseMap struct {
|
||||
*noise.Perlin
|
||||
}
|
||||
|
||||
// Value generates the noise value for an x/y pair. The range of the output is approximately [-1.325825214,1.325825214] (for 4 harmonics).
|
||||
func (m randomNoiseMap) Value(x, y int32) float64 {
|
||||
value := m.Noise2D(float64(x)*.53, float64(y)*.53, 1.01, 2, 2)*.5 + .5
|
||||
return clipNormalized(value)
|
||||
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
|
||||
}
|
||||
|
||||
func NewGame() *Game {
|
||||
@ -76,18 +43,52 @@ func NewGame() *Game {
|
||||
Variant: NewRandomNoiseMap(rand.Int63()),
|
||||
PlaceX: NewRandomNoiseMap(rand.Int63()),
|
||||
PlaceY: NewRandomNoiseMap(rand.Int63()),
|
||||
Flowers: map[Point]Flower{},
|
||||
}
|
||||
terrain.AddFlower(Pt(0, 0), NewPoppyTraits())
|
||||
return &Game{
|
||||
Money: 100,
|
||||
Terrain: terrain,
|
||||
Flowers: []Flower{},
|
||||
Bees: []Bee{},
|
||||
|
||||
start: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
type Flower struct {
|
||||
Location PointF
|
||||
func (g *Game) Update() {
|
||||
update := time.Since(g.start)
|
||||
for g.lastUpdate < update {
|
||||
g.tick()
|
||||
g.lastUpdate += time.Millisecond * 10
|
||||
}
|
||||
}
|
||||
|
||||
type Bee struct {
|
||||
Location PointF
|
||||
func (g *Game) tick() {
|
||||
randomNeighbor := func(pos Point) Point {
|
||||
switch rand.Intn(4) {
|
||||
case 0:
|
||||
return Pt(pos.X-1, pos.Y)
|
||||
case 1:
|
||||
return Pt(pos.X, pos.Y-1)
|
||||
case 2:
|
||||
return Pt(pos.X+1, pos.Y)
|
||||
case 3:
|
||||
return Pt(pos.X, pos.Y+1)
|
||||
}
|
||||
return pos
|
||||
}
|
||||
flowers := map[Point]Flower{}
|
||||
for pos, flower := range g.Terrain.Flowers {
|
||||
if rand.Float32() < flower.Traits.Spread {
|
||||
dst := randomNeighbor(pos)
|
||||
if _, ok := g.Terrain.Flowers[dst]; !ok {
|
||||
if _, ok := flowers[dst]; !ok {
|
||||
flowers[dst] = g.Terrain.NewFlower(dst, flower.Traits)
|
||||
}
|
||||
}
|
||||
}
|
||||
if rand.Float32() < flower.Traits.Life*flower.Traits.LifeModifier {
|
||||
flowers[pos] = flower
|
||||
}
|
||||
}
|
||||
g.Terrain.Flowers = flowers
|
||||
}
|
||||
|
37
math.go
Normal file
@ -0,0 +1,37 @@
|
||||
package tins2020
|
||||
|
||||
import "math"
|
||||
|
||||
func Abs32(x float32) float32 {
|
||||
if x < 0 {
|
||||
return -x
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
func AbsSub32(a, b float32) float32 {
|
||||
if a > b {
|
||||
return a - b
|
||||
}
|
||||
return b - a
|
||||
}
|
||||
|
||||
func Ceil32(x float32) float32 { return float32(math.Ceil(float64(x))) }
|
||||
|
||||
func Floor32(x float32) float32 { return float32(math.Floor(float64(x))) }
|
||||
|
||||
func Max32(a, b float32) float32 {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func Min32(a, b float32) float32 {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func Sqrt32(x float32) float32 { return float32(math.Sqrt(float64(x))) }
|
52
noisemap.go
Normal file
@ -0,0 +1,52 @@
|
||||
package tins2020
|
||||
|
||||
import "opslag.de/schobers/geom/noise"
|
||||
|
||||
type NoiseMap interface {
|
||||
Value(x, y int32) float64
|
||||
}
|
||||
|
||||
func NewNoiseMap(seed int64) NoiseMap {
|
||||
return &noiseMap{
|
||||
noise: noise.NewPerlin(seed),
|
||||
alpha: 2,
|
||||
beta: 4,
|
||||
harmonics: 2,
|
||||
}
|
||||
}
|
||||
|
||||
func NewRandomNoiseMap(seed int64) NoiseMap {
|
||||
return &randomNoiseMap{noise.NewPerlin(seed)}
|
||||
}
|
||||
|
||||
type noiseMap struct {
|
||||
noise *noise.Perlin
|
||||
alpha, beta float64
|
||||
harmonics int
|
||||
}
|
||||
|
||||
func clipNormalized(x float64) float64 {
|
||||
if x < 0 {
|
||||
return 0
|
||||
}
|
||||
if x > 1 {
|
||||
return 1
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// Value generates the noise value for an x/y pair. The range of the output is approximately [-1.325825214,1.325825214] (for 4 harmonics).
|
||||
func (m *noiseMap) Value(x, y int32) float64 {
|
||||
value := m.noise.Noise2D(float64(x)*.01, float64(y)*.01, m.alpha, m.beta, m.harmonics)*.565 + .5
|
||||
return clipNormalized(value)
|
||||
}
|
||||
|
||||
type randomNoiseMap struct {
|
||||
*noise.Perlin
|
||||
}
|
||||
|
||||
// Value generates the noise value for an x/y pair. The range of the output is approximately [-1.325825214,1.325825214] (for 4 harmonics).
|
||||
func (m randomNoiseMap) Value(x, y int32) float64 {
|
||||
value := m.Noise2D(float64(x)*.53, float64(y)*.53, 1.01, 2, 2)*.5 + .5
|
||||
return clipNormalized(value)
|
||||
}
|
90
projection.go
Normal file
@ -0,0 +1,90 @@
|
||||
package tins2020
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
type projection struct {
|
||||
center PointF
|
||||
zoom float32
|
||||
zoomInv float32
|
||||
|
||||
windowRect sdl.Rect
|
||||
tileScreenDelta PointF
|
||||
tileScreenDeltaInv PointF
|
||||
tileScreenOffset Point
|
||||
tileScreenSize Point
|
||||
tileFitScreenSize Point
|
||||
windowCenter Point
|
||||
}
|
||||
|
||||
func newProjection() projection {
|
||||
return projection{zoom: 1, tileScreenDelta: PtF(65, 32), tileScreenDeltaInv: PtF(1./65, 1./32)}
|
||||
}
|
||||
|
||||
func (p *projection) mapToScreen(x, y int32) Point {
|
||||
return p.mapToScreenF(float32(x), float32(y))
|
||||
}
|
||||
|
||||
func (p *projection) mapToScreenF(x, y float32) Point {
|
||||
translated := PtF(x-p.center.X, y-p.center.Y)
|
||||
return Pt(p.windowCenter.X+int32((translated.X-translated.Y)*65*p.zoomInv), p.windowCenter.Y+int32((translated.X+translated.Y)*32*p.zoomInv))
|
||||
}
|
||||
|
||||
func (p *projection) screenToMap(x, y int32) PointF {
|
||||
pos := p.screenToMapRel(x-p.windowCenter.X, y-p.windowCenter.Y)
|
||||
return p.center.Add(pos)
|
||||
}
|
||||
|
||||
func (p *projection) screenToMapRel(x, y int32) PointF {
|
||||
normX := p.zoom * float32(x)
|
||||
normY := p.zoom * float32(y)
|
||||
return PtF(.5*(p.tileScreenDeltaInv.X*normX+p.tileScreenDeltaInv.Y*normY), .5*(-p.tileScreenDeltaInv.X*normX+p.tileScreenDeltaInv.Y*normY))
|
||||
}
|
||||
|
||||
func (p *projection) screenToTileFitRect(pos Point) *sdl.Rect {
|
||||
return &sdl.Rect{X: pos.X - p.tileFitScreenSize.X, Y: pos.Y - p.tileFitScreenSize.Y, W: 2 * p.tileFitScreenSize.X, H: 2 * p.tileFitScreenSize.Y}
|
||||
}
|
||||
|
||||
func (p *projection) screenToTileRect(pos Point) *sdl.Rect {
|
||||
return &sdl.Rect{X: pos.X - p.tileScreenOffset.X, Y: pos.Y - p.tileScreenOffset.Y, W: p.tileScreenSize.X, H: p.tileScreenSize.Y}
|
||||
}
|
||||
|
||||
func (p *projection) update(renderer *sdl.Renderer) {
|
||||
p.zoomInv = 1 / p.zoom
|
||||
|
||||
p.tileScreenOffset = Pt(int32(p.zoomInv*256), int32(p.zoomInv*300))
|
||||
p.tileScreenSize = Pt(int32(p.zoomInv*512), int32(p.zoomInv*512))
|
||||
p.tileFitScreenSize = Pt(int32(p.zoomInv*65), int32(p.zoomInv*32))
|
||||
|
||||
windowW, windowH, err := renderer.GetOutputSize()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
p.windowCenter = Pt(windowW/2, windowH/2)
|
||||
p.windowRect = sdl.Rect{X: 0, Y: 0, W: windowW - 0, H: windowH - 0}
|
||||
}
|
||||
|
||||
func (p *projection) visibleTiles(action func(int32, int32, Point)) {
|
||||
topLeft := p.screenToMap(p.windowRect.X, p.windowRect.Y)
|
||||
topRight := p.screenToMap(p.windowRect.X+p.windowRect.W, p.windowRect.Y)
|
||||
bottomLeft := p.screenToMap(p.windowRect.X, p.windowRect.Y+p.windowRect.H)
|
||||
bottomRight := p.screenToMap(p.windowRect.X+p.windowRect.W, p.windowRect.Y+p.windowRect.H)
|
||||
minY, maxY := int32(Floor32(topRight.Y)), int32(Ceil32(bottomLeft.Y))
|
||||
minX, maxX := int32(Floor32(topLeft.X)), int32(Ceil32(bottomRight.X))
|
||||
for y := minY; y <= maxY; y++ {
|
||||
for x := minX; x <= maxX; x++ {
|
||||
pos := p.mapToScreen(x, y)
|
||||
rectFit := p.screenToTileFitRect(pos)
|
||||
if rectFit.X+rectFit.W < p.windowRect.X || rectFit.Y+rectFit.H < p.windowRect.Y {
|
||||
continue
|
||||
}
|
||||
if rectFit.X > p.windowRect.X+p.windowRect.W || rectFit.Y > p.windowRect.Y+p.windowRect.H {
|
||||
break
|
||||
}
|
||||
action(x, y, pos)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
package tins2020
|
||||
|
||||
import "github.com/veandco/go-sdl2/sdl"
|
||||
|
||||
type Control interface {
|
||||
Init(*Context) error
|
||||
Handle(*Context, sdl.Event)
|
||||
Render(*Context)
|
||||
}
|
51
resourceloader.go
Normal file
@ -0,0 +1,51 @@
|
||||
package tins2020
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ResourceLoader struct {
|
||||
Resources map[string]string
|
||||
}
|
||||
|
||||
func NewResourceLoader() *ResourceLoader {
|
||||
return &ResourceLoader{}
|
||||
}
|
||||
|
||||
func (l *ResourceLoader) parseResourcesFile(res *Resources, name string) error {
|
||||
f, err := res.Fs().Open(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
l.Resources = map[string]string{}
|
||||
scanner := bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
definition := scanner.Text()
|
||||
sep := strings.Index(definition, ":")
|
||||
if sep == -1 {
|
||||
continue
|
||||
}
|
||||
name := strings.TrimSpace(definition[:sep])
|
||||
content := strings.TrimSpace(definition[sep+1:])
|
||||
l.Resources[name] = content
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *ResourceLoader) LoadFromFile(res *Resources, name string, action func(string, string) error) error {
|
||||
err := l.parseResourcesFile(res, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for name, content := range l.Resources {
|
||||
err = action(name, content)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot load resource '%s'; error: %v", name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
@ -31,4 +31,5 @@ func (s *Settings) Store() error {
|
||||
|
||||
type WindowSettings struct {
|
||||
Location *Point
|
||||
Size *Point
|
||||
}
|
||||
|
@ -2,11 +2,9 @@ package tins2020
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"opslag.de/schobers/geom"
|
||||
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
"opslag.de/schobers/geom"
|
||||
)
|
||||
|
||||
type terrainRenderer struct {
|
||||
@ -23,59 +21,6 @@ type interaction struct {
|
||||
mouseDrag *Point
|
||||
}
|
||||
|
||||
type projection struct {
|
||||
center PointF
|
||||
zoom float32
|
||||
zoomInv float32
|
||||
|
||||
tileScreenDelta PointF
|
||||
tileScreenDeltaInv PointF
|
||||
tileScreenOffset Point
|
||||
tileScreenSize Point
|
||||
windowCenter Point
|
||||
}
|
||||
|
||||
func newProjection() projection {
|
||||
return projection{zoom: 1, tileScreenDelta: PtF(65, 32), tileScreenDeltaInv: PtF(1./65, 1./32)}
|
||||
}
|
||||
|
||||
func (p *projection) update(renderer *sdl.Renderer) {
|
||||
p.zoomInv = 1 / p.zoom
|
||||
|
||||
p.tileScreenOffset = Pt(int32(p.zoomInv*256), int32(p.zoomInv*300))
|
||||
p.tileScreenSize = Pt(int32(p.zoomInv*512), int32(p.zoomInv*512))
|
||||
|
||||
windowW, windowH, err := renderer.GetOutputSize()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
p.windowCenter = Pt(windowW/2, windowH/2)
|
||||
}
|
||||
|
||||
func (p *projection) mapToScreen(x, y int32) Point {
|
||||
return p.mapToScreenF(float32(x), float32(y))
|
||||
}
|
||||
|
||||
func (p *projection) mapToScreenF(x, y float32) Point {
|
||||
translated := PtF(x-p.center.X, y-p.center.Y)
|
||||
return Pt(p.windowCenter.X+int32((translated.X-translated.Y)*65*p.zoomInv), p.windowCenter.Y+int32((translated.X+translated.Y)*32*p.zoomInv))
|
||||
}
|
||||
|
||||
func (p *projection) screenToMapRel(x, y int32) PointF {
|
||||
normX := p.zoom * float32(x)
|
||||
normY := p.zoom * float32(y)
|
||||
return PtF(.5*(p.tileScreenDeltaInv.X*normX+p.tileScreenDeltaInv.Y*normY), .5*(-p.tileScreenDeltaInv.X*normX+p.tileScreenDeltaInv.Y*normY))
|
||||
}
|
||||
|
||||
func (p *projection) screenToMap(x, y int32) PointF {
|
||||
pos := p.screenToMapRel(x-p.windowCenter.X, y-p.windowCenter.Y)
|
||||
return p.center.Add(pos)
|
||||
}
|
||||
|
||||
func (p *projection) screenToTileRect(pos Point) *sdl.Rect {
|
||||
return &sdl.Rect{X: pos.X - p.tileScreenOffset.X, Y: pos.Y - p.tileScreenOffset.Y, W: p.tileScreenSize.X, H: p.tileScreenSize.Y}
|
||||
}
|
||||
|
||||
func NewTerrainRenderer(terrain *Map) Control {
|
||||
return &terrainRenderer{terrain: terrain, project: newProjection()}
|
||||
}
|
||||
@ -92,25 +37,22 @@ func (r *terrainRenderer) Handle(ctx *Context, event sdl.Event) {
|
||||
r.interact.mouseLeftDown = e.Type == sdl.MOUSEBUTTONDOWN
|
||||
if r.interact.mouseLeftDown && r.interact.mouseDrag == nil {
|
||||
r.interact.mouseDrag = &Point{e.X, e.Y}
|
||||
fmt.Println("Drag start", r.interact.mouseDrag)
|
||||
} else if !r.interact.mouseLeftDown && r.interact.mouseDrag != nil {
|
||||
fmt.Println("Drag end", r.interact.mouseDrag)
|
||||
r.interact.mouseDrag = nil
|
||||
}
|
||||
}
|
||||
case *sdl.MouseMotionEvent:
|
||||
r.hover = r.project.screenToMap(e.X, e.Y)
|
||||
if r.interact.mouseDrag != nil {
|
||||
fmt.Println("Dragging...", r.project.center)
|
||||
r.project.center = r.project.center.Sub(r.project.screenToMapRel(e.X-r.interact.mouseDrag.X, e.Y-r.interact.mouseDrag.Y))
|
||||
r.project.update(ctx.Renderer)
|
||||
r.interact.mouseDrag = &Point{e.X, e.Y}
|
||||
}
|
||||
case *sdl.MouseWheelEvent:
|
||||
if e.Y > 0 {
|
||||
if e.Y > 0 && r.project.zoom > .5 {
|
||||
r.project.zoom *= .5
|
||||
r.project.update(ctx.Renderer)
|
||||
} else if e.Y < 0 {
|
||||
} else if e.Y < 0 && r.project.zoom < 4 {
|
||||
r.project.zoom *= 2
|
||||
r.project.update(ctx.Renderer)
|
||||
}
|
||||
@ -185,41 +127,48 @@ func (r *terrainRenderer) Render(ctx *Context) {
|
||||
return variantToTexture("bush-large-%d", stretch(variant, .8, 1)*multiplier)
|
||||
}
|
||||
|
||||
toItemTexture := func(x, y int32) *Texture {
|
||||
variant := r.terrain.Variant.Value(x, y)
|
||||
_, ok := r.terrain.Flowers[Pt(x, y)]
|
||||
if ok {
|
||||
return variantToTexture("flower-poppy-%d", variant)
|
||||
}
|
||||
temp := r.terrain.Temp.Value(x, y)
|
||||
humid := r.terrain.Humid.Value(x, y)
|
||||
return toPropTexture(temp, humid, variant)
|
||||
}
|
||||
|
||||
// horizontal: [191, 321) = 130
|
||||
// vertical: [267,332) = 65
|
||||
|
||||
hover := Pt(int32(geom.Round32(r.hover.X)), int32(geom.Round32(r.hover.Y)))
|
||||
for y := int32(-100); y < 100; y++ {
|
||||
for x := int32(-100); x < 100; x++ {
|
||||
r.project.visibleTiles(func(x, y int32, pos Point) {
|
||||
if x == hover.X && y == hover.Y {
|
||||
continue
|
||||
return
|
||||
}
|
||||
temp := r.terrain.Temp.Value(x, y)
|
||||
humid := r.terrain.Humid.Value(x, y)
|
||||
text := toTileTexture(temp, humid)
|
||||
pos := r.project.mapToScreen(x, y)
|
||||
text.Copy(ctx.Renderer, r.project.screenToTileRect(pos))
|
||||
}
|
||||
}
|
||||
rect := r.project.screenToTileRect(pos)
|
||||
text.Copy(ctx.Renderer, rect)
|
||||
})
|
||||
|
||||
for y := int32(-100); y < 100; y++ {
|
||||
for x := int32(-100); x < 100; x++ {
|
||||
variant := r.terrain.Variant.Value(x, y)
|
||||
if variant < -1 || variant > 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
temp := r.terrain.Temp.Value(x, y)
|
||||
humid := r.terrain.Humid.Value(x, y)
|
||||
text := toPropTexture(temp, humid, variant)
|
||||
r.project.visibleTiles(func(x, y int32, pos Point) {
|
||||
text := toItemTexture(x, y)
|
||||
if text == nil {
|
||||
continue
|
||||
return
|
||||
}
|
||||
|
||||
placeX, placeY := r.terrain.PlaceX.Value(x, y), r.terrain.PlaceY.Value(x, y)
|
||||
pos := r.project.mapToScreenF(float32(x)-.2+float32(.8*placeX-.4), float32(y)-.2+float32(.8*placeY-.4))
|
||||
|
||||
pos = r.project.mapToScreenF(float32(x)-.2+float32(.8*placeX-.4), float32(y)-.2+float32(.8*placeY-.4))
|
||||
text.Copy(ctx.Renderer, r.project.screenToTileRect(pos))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// gfx.RectangleColor(ctx.Renderer, r.project.windowRect.X, r.project.windowRect.Y, r.project.windowRect.X+r.project.windowRect.W, r.project.windowRect.Y+r.project.windowRect.H, sdl.Color{R: 255, A: 255})
|
||||
// for y := int32(-40); y < 40; y++ {
|
||||
// for x := int32(-40); x < 40; x++ {
|
||||
// pos := r.project.mapToScreen(x, y)
|
||||
// ctx.Fonts.Font("debug").RenderCopy(ctx.Renderer, fmt.Sprintf("(%d, %d)", x, y), pos, sdl.Color{R: 255, A: 255})
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
@ -24,6 +24,10 @@ func NewTextureFromSurface(renderer *sdl.Renderer, surface *sdl.Surface) (*Textu
|
||||
|
||||
func (t *Texture) Rect() *sdl.Rect { return t.rect }
|
||||
|
||||
func (t *Texture) RectOffset(offset Point) *sdl.Rect {
|
||||
return &sdl.Rect{X: offset.X, Y: offset.Y, W: t.rect.W, H: t.rect.H}
|
||||
}
|
||||
|
||||
func (t *Texture) Copy(renderer *sdl.Renderer, target *sdl.Rect) {
|
||||
renderer.Copy(t.texture, t.rect, target)
|
||||
}
|
||||
|