Add power scan
Zones are now scanned for whether or not they are connected to a power plant.
This commit is contained in:
parent
69a012bedd
commit
6dbd53e9c4
|
@ -35,3 +35,4 @@ Please share issues and suggestions [here](https://code.rocketnine.space/tslocum
|
|||
- [ebiten](https://github.com/hajimehoshi/ebiten) - Game engine
|
||||
- [gohan](https://code.rocketnine.space/tslocum/gohan) - Entity Component System framework
|
||||
- [go-tiled](https://github.com/lafriks/go-tiled) - Tiled map file (.TMX) parser
|
||||
- [go-astar](https://github.com/beefsack/go-astar) - Pathfinding library
|
||||
|
|
|
@ -19,19 +19,21 @@ const sampleRate = 44100
|
|||
//go:embed image map sound
|
||||
var FS embed.FS
|
||||
|
||||
var ImgWhiteSquare = ebiten.NewImage(64, 64)
|
||||
var ImgBlackSquare = ebiten.NewImage(64, 64)
|
||||
var ImgBlank = ebiten.NewImage(1, 1)
|
||||
|
||||
var SoundMusic *audio.Player
|
||||
var SoundSelect *audio.Player
|
||||
var (
|
||||
ImgBlank = ebiten.NewImage(1, 1)
|
||||
ImgWhiteSquare = ebiten.NewImage(64, 64)
|
||||
ImgBlackSquare = ebiten.NewImage(64, 64)
|
||||
ImgPower = LoadImage("image/power.png")
|
||||
)
|
||||
|
||||
var (
|
||||
SoundPop1 *audio.Player
|
||||
SoundPop2 *audio.Player
|
||||
SoundPop3 *audio.Player
|
||||
SoundPop4 *audio.Player
|
||||
SoundPop5 *audio.Player
|
||||
SoundMusic *audio.Player
|
||||
SoundSelect *audio.Player
|
||||
SoundPop1 *audio.Player
|
||||
SoundPop2 *audio.Player
|
||||
SoundPop3 *audio.Player
|
||||
SoundPop4 *audio.Player
|
||||
SoundPop5 *audio.Player
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 8.1 KiB |
|
@ -300,6 +300,11 @@ func (g *game) Draw(screen *ebiten.Image) {
|
|||
continue
|
||||
}
|
||||
drawn += g.renderSprite(float64(x), float64(y), 0, float64(i*-40), 0, 1, colorScale, alpha, false, false, sprite, screen)
|
||||
|
||||
// Draw power-outs.
|
||||
if world.World.Ticks%(144*2) < int(144.0*1.5) && world.World.PowerOuts[x][y] {
|
||||
drawn += g.renderSprite(float64(x), float64(y), 0, -52, 0, 1, 1, 1, false, false, asset.ImgPower, screen)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -331,7 +336,6 @@ func (g *game) addSystems() {
|
|||
g.renderSystem = system.NewRenderSystem()
|
||||
ecs.AddSystem(g.renderSystem)
|
||||
ecs.AddSystem(system.NewRenderHudSystem())
|
||||
ecs.AddSystem(system.NewRenderMessageSystem())
|
||||
ecs.AddSystem(system.NewRenderDebugTextSystem(world.World.Player))
|
||||
ecs.AddSystem(system.NewProfileSystem(world.World.Player))
|
||||
}
|
||||
|
|
1
go.mod
1
go.mod
|
@ -4,6 +4,7 @@ go 1.17
|
|||
|
||||
require (
|
||||
code.rocketnine.space/tslocum/gohan v0.0.0-20220106015515-0231e09ad78e
|
||||
github.com/beefsack/go-astar v0.0.0-20200827232313-4ecf9e304482
|
||||
github.com/hajimehoshi/ebiten/v2 v2.2.4
|
||||
github.com/lafriks/go-tiled v0.7.0
|
||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410
|
||||
|
|
2
go.sum
2
go.sum
|
@ -1,6 +1,8 @@
|
|||
code.rocketnine.space/tslocum/gohan v0.0.0-20220106015515-0231e09ad78e h1:n/4oueA0I1ilZpFGLlaCqaEDTX658fVQDtRkahdUDUI=
|
||||
code.rocketnine.space/tslocum/gohan v0.0.0-20220106015515-0231e09ad78e/go.mod h1:nOvFBFvFPl5sDtkMy2Fn/7QZcWq5RE98/mK+INLqIWg=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/beefsack/go-astar v0.0.0-20200827232313-4ecf9e304482 h1:p4g4uok3+r6Tg6fxXEQUAcMAX/WdK6WhkQW9s0jaT7k=
|
||||
github.com/beefsack/go-astar v0.0.0-20200827232313-4ecf9e304482/go.mod h1:Cu3t5VeqE8kXjUBeNXWQprfuaP5UCIc5ggGjgMx9KFc=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2/audio"
|
||||
|
||||
|
@ -159,7 +160,7 @@ func (s *playerMoveSystem) Update(ctx *gohan.Context) error {
|
|||
}
|
||||
if ebiten.IsMouseButtonPressed(ebiten.MouseButtonMiddle) {
|
||||
if s.scrollDragX == -1 && s.scrollDragY == -1 {
|
||||
// TODO disabled due to possible ebiten bug
|
||||
// TODO Disabled due to possible ebiten bug.
|
||||
//ebiten.SetCursorMode(ebiten.CursorModeCaptured)
|
||||
|
||||
s.scrollDragX, s.scrollDragY = x, y
|
||||
|
@ -186,6 +187,19 @@ func (s *playerMoveSystem) Update(ctx *gohan.Context) error {
|
|||
}
|
||||
}
|
||||
}
|
||||
// Clamp viewport.
|
||||
minCam := -256.0 * world.TileSize / 2
|
||||
maxCam := 256.0 * world.TileSize / 2
|
||||
if world.World.CamX < minCam {
|
||||
world.World.CamX = minCam
|
||||
} else if world.World.CamX > maxCam {
|
||||
world.World.CamX = maxCam
|
||||
}
|
||||
if world.World.CamY < 0 {
|
||||
world.World.CamY = 0
|
||||
} else if world.World.CamY > maxCam {
|
||||
world.World.CamY = maxCam
|
||||
}
|
||||
|
||||
if x < world.SidebarWidth {
|
||||
world.World.Level.ClearHoverSprites()
|
||||
|
@ -198,6 +212,12 @@ func (s *playerMoveSystem) Update(ctx *gohan.Context) error {
|
|||
if button.StructureType == world.StructureToggleTransparentStructures {
|
||||
world.World.TransparentStructures = !world.World.TransparentStructures
|
||||
world.World.HUDUpdated = true
|
||||
|
||||
if world.World.TransparentStructures {
|
||||
world.ShowMessage("Enabled transparency", 3)
|
||||
} else {
|
||||
world.ShowMessage("Disabled transparency", 3)
|
||||
}
|
||||
} else {
|
||||
if world.World.HoverStructure == button.StructureType {
|
||||
world.SetHoverStructure(0) // Deselect.
|
||||
|
@ -222,8 +242,32 @@ func (s *playerMoveSystem) Update(ctx *gohan.Context) error {
|
|||
} else {
|
||||
world.World.Level.ClearHoverSprites()
|
||||
|
||||
_, err := world.BuildStructure(world.World.HoverStructure, false, int(tileX), int(tileY))
|
||||
// TODO draw hovers and build all roads in a line from drag start
|
||||
|
||||
s, err := world.BuildStructure(world.World.HoverStructure, false, int(tileX), int(tileY))
|
||||
if err == nil {
|
||||
tileX, tileY = float64(s.X), float64(s.Y)
|
||||
|
||||
isPowerPlant := world.World.HoverStructure == world.StructurePowerPlantCoal
|
||||
if isPowerPlant {
|
||||
plant := &world.PowerPlant{
|
||||
Type: world.World.HoverStructure,
|
||||
X: int(tileX),
|
||||
Y: int(tileY),
|
||||
}
|
||||
world.World.PowerPlants = append(world.World.PowerPlants, plant)
|
||||
}
|
||||
|
||||
isZone := world.World.HoverStructure == world.StructureResidentialZone || world.World.HoverStructure == world.StructureCommercialZone || world.World.HoverStructure == world.StructureIndustrialZone
|
||||
if isZone {
|
||||
zone := &world.Zone{
|
||||
Type: world.World.HoverStructure,
|
||||
X: int(tileX),
|
||||
Y: int(tileY),
|
||||
}
|
||||
world.World.Zones = append(world.World.Zones, zone)
|
||||
}
|
||||
|
||||
if world.World.HoverStructure != world.StructureBulldozer {
|
||||
sounds := []*audio.Player{
|
||||
asset.SoundPop2,
|
||||
|
@ -235,6 +279,16 @@ func (s *playerMoveSystem) Update(ctx *gohan.Context) error {
|
|||
}
|
||||
world.World.Funds -= cost
|
||||
|
||||
if world.World.HoverStructure == world.StructureResidentialZone {
|
||||
world.ShowMessage(world.World.Printer.Sprintf("Zoned area for residential use (-$%d)", cost), 3)
|
||||
} else if world.World.HoverStructure == world.StructureCommercialZone {
|
||||
world.ShowMessage(world.World.Printer.Sprintf("Zoned area for commercial use (-$%d)", cost), 3)
|
||||
} else if world.World.HoverStructure == world.StructureIndustrialZone {
|
||||
world.ShowMessage(world.World.Printer.Sprintf("Zoned area for industrial use (-$%d)", cost), 3)
|
||||
} else {
|
||||
world.ShowMessage(world.World.Printer.Sprintf("Built %s (-$%d)", strings.ToLower(world.StructureTooltips[world.World.HoverStructure]), cost), 3)
|
||||
}
|
||||
|
||||
world.World.HUDUpdated = true
|
||||
}
|
||||
world.BuildStructure(world.World.HoverStructure, true, int(tileX), int(tileY))
|
||||
|
|
|
@ -33,6 +33,8 @@ func (s *PopulateSystem) Update(_ *gohan.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
const popDuration = 144 * 4
|
||||
|
||||
// Thresholds.
|
||||
const (
|
||||
lowDensity = 3
|
||||
|
@ -42,6 +44,8 @@ func (s *PopulateSystem) Update(_ *gohan.Context) error {
|
|||
switch structureType {
|
||||
case world.StructureResidentialZone:
|
||||
switch {
|
||||
case population == 0:
|
||||
return world.StructureResidentialZone
|
||||
case population <= lowDensity:
|
||||
return world.StructureResidentialLow
|
||||
case population <= mediumDensity:
|
||||
|
@ -51,6 +55,8 @@ func (s *PopulateSystem) Update(_ *gohan.Context) error {
|
|||
}
|
||||
case world.StructureCommercialZone:
|
||||
switch {
|
||||
case population == 0:
|
||||
return world.StructureCommercialZone
|
||||
case population <= lowDensity:
|
||||
return world.StructureCommercialLow
|
||||
case population <= mediumDensity:
|
||||
|
@ -60,6 +66,8 @@ func (s *PopulateSystem) Update(_ *gohan.Context) error {
|
|||
}
|
||||
case world.StructureIndustrialZone:
|
||||
switch {
|
||||
case population == 0:
|
||||
return world.StructureIndustrialZone
|
||||
case population <= lowDensity:
|
||||
return world.StructureIndustrialLow
|
||||
case population <= mediumDensity:
|
||||
|
@ -73,12 +81,51 @@ func (s *PopulateSystem) Update(_ *gohan.Context) error {
|
|||
}
|
||||
|
||||
const maxPopulation = 10
|
||||
if world.World.Ticks%144 == 0 {
|
||||
if world.World.Ticks%popDuration == 0 {
|
||||
popR, popC, popI := world.Population()
|
||||
targetR, targetC, targetI := world.TargetPopulation()
|
||||
for _, zone := range world.World.Zones {
|
||||
if zone.Population < maxPopulation {
|
||||
var offset int
|
||||
if zone.Type == world.StructureResidentialZone {
|
||||
if popR < targetR {
|
||||
offset = 1
|
||||
} else if popR > targetR {
|
||||
offset = -1
|
||||
}
|
||||
} else if zone.Type == world.StructureCommercialZone {
|
||||
if popC < targetC {
|
||||
offset = 1
|
||||
} else if popC > targetC {
|
||||
offset = -1
|
||||
}
|
||||
} else { // Industrial
|
||||
if popI < targetI {
|
||||
offset = 1
|
||||
} else if popI > targetI {
|
||||
offset = -1
|
||||
}
|
||||
}
|
||||
if offset == -1 && zone.Population > 0 {
|
||||
zone.Population--
|
||||
if zone.Type == world.StructureResidentialZone {
|
||||
popR--
|
||||
} else if zone.Type == world.StructureCommercialZone {
|
||||
popC--
|
||||
} else { // Industrial
|
||||
popI--
|
||||
}
|
||||
} else if offset == 1 && zone.Population < maxPopulation && zone.Powered {
|
||||
zone.Population++
|
||||
if zone.Type == world.StructureResidentialZone {
|
||||
popR++
|
||||
} else if zone.Type == world.StructureCommercialZone {
|
||||
popC++
|
||||
} else { // Industrial
|
||||
popI++
|
||||
}
|
||||
}
|
||||
newType := buildStructureType(zone.Type, zone.Population)
|
||||
// TODO only bulldoze when changed
|
||||
for offsetX := 0; offsetX < 2; offsetX++ {
|
||||
for offsetY := 0; offsetY < 2; offsetY++ {
|
||||
world.BuildStructure(world.StructureBulldozer, false, zone.X-offsetX, zone.Y-offsetY)
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/beefsack/go-astar"
|
||||
|
||||
"code.rocketnine.space/tslocum/citylimits/component"
|
||||
"code.rocketnine.space/tslocum/citylimits/world"
|
||||
"code.rocketnine.space/tslocum/gohan"
|
||||
|
@ -33,9 +37,142 @@ func (s *PowerScanSystem) Update(_ *gohan.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
const scanTicks = 144 * 2
|
||||
if world.World.Ticks%scanTicks != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !world.World.PowerUpdated {
|
||||
return nil
|
||||
}
|
||||
|
||||
var totalPowerAvailable int
|
||||
|
||||
powerRemaining := make([]int, len(world.World.PowerPlants))
|
||||
for i, plant := range world.World.PowerPlants {
|
||||
powerRemaining[i] = world.PowerPlantCapacities[plant.Type]
|
||||
totalPowerAvailable += world.PowerPlantCapacities[plant.Type]
|
||||
}
|
||||
|
||||
const (
|
||||
plantSize = 5
|
||||
)
|
||||
powerSourceTiles := make([][]*world.PowerMapTile, len(world.World.PowerPlants))
|
||||
log.Println("POWER TILES")
|
||||
for i, plant := range world.World.PowerPlants {
|
||||
for y := 0; y < plantSize; y++ {
|
||||
t := world.World.Power.GetTile(plant.X+1, plant.Y-y)
|
||||
if t != nil {
|
||||
powerSourceTiles[i] = append(powerSourceTiles[i], t)
|
||||
}
|
||||
|
||||
t = world.World.Power.GetTile(plant.X-plantSize, plant.Y-y)
|
||||
if t != nil {
|
||||
powerSourceTiles[i] = append(powerSourceTiles[i], t)
|
||||
}
|
||||
}
|
||||
for x := 0; x < plantSize; x++ {
|
||||
t := world.World.Power.GetTile(plant.X-x, plant.Y+1)
|
||||
if t != nil {
|
||||
powerSourceTiles[i] = append(powerSourceTiles[i], t)
|
||||
}
|
||||
|
||||
t = world.World.Power.GetTile(plant.X, plant.Y-plantSize)
|
||||
if t != nil {
|
||||
powerSourceTiles[i] = append(powerSourceTiles[i], t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var totalPowerRequired int
|
||||
|
||||
var havePowerOut bool
|
||||
|
||||
world.ResetPowerOuts()
|
||||
|
||||
// TODO use a consistent procedure to check each building that needs power
|
||||
// as connected via road to a power plant, and power-out buildings without enough power
|
||||
// "citizens report brown-outs"
|
||||
for _, zone := range world.World.Zones {
|
||||
// TODO lock, set powered status on build immediately
|
||||
|
||||
powerRequired := world.ZonePowerRequirement[zone.Type]
|
||||
_ = powerRequired
|
||||
|
||||
const zoneSize = 2
|
||||
var powerDestinationTiles []*world.PowerMapTile
|
||||
for y := 0; y < zoneSize; y++ {
|
||||
t := world.World.Power.GetTile(zone.X+1, zone.Y-y)
|
||||
if t != nil {
|
||||
powerDestinationTiles = append(powerDestinationTiles, t)
|
||||
}
|
||||
|
||||
t = world.World.Power.GetTile(zone.X-zoneSize, zone.Y-y)
|
||||
if t != nil {
|
||||
powerDestinationTiles = append(powerDestinationTiles, t)
|
||||
}
|
||||
}
|
||||
for x := 0; x < zoneSize; x++ {
|
||||
t := world.World.Power.GetTile(zone.X-x, zone.Y+1)
|
||||
if t != nil {
|
||||
powerDestinationTiles = append(powerDestinationTiles, t)
|
||||
}
|
||||
|
||||
t = world.World.Power.GetTile(zone.X, zone.Y-zoneSize)
|
||||
if t != nil {
|
||||
powerDestinationTiles = append(powerDestinationTiles, t)
|
||||
}
|
||||
}
|
||||
|
||||
var powered bool
|
||||
FINDPOWERPATH:
|
||||
for j := range powerRemaining {
|
||||
if powerRemaining[j] < powerRequired {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, powerSource := range powerSourceTiles[j] {
|
||||
for _, to := range powerDestinationTiles {
|
||||
if to == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
from := world.World.Power.GetTile(powerSource.X, powerSource.Y)
|
||||
|
||||
log.Println("SEARCH", from.X, from.Y, "TO", to.X, to.Y)
|
||||
|
||||
/*for _, n := range powerSource.PathNeighbors() {
|
||||
t := n.(*world.PowerMapTile)
|
||||
log.Println("NEIGHBOR", t.X, t.Y, t.CarriesPower)
|
||||
}*/
|
||||
|
||||
p, dist, found := astar.Path(from, to)
|
||||
if found {
|
||||
log.Printf("Resulting path\n%+v %f", p, dist)
|
||||
powerRemaining[j] -= powerRequired
|
||||
powered = true
|
||||
break FINDPOWERPATH
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
zone.Powered = powered
|
||||
log.Println("ZONE", zone, zone.Powered)
|
||||
|
||||
if !powered {
|
||||
havePowerOut = true
|
||||
world.World.PowerOuts[zone.X][zone.Y] = true
|
||||
}
|
||||
|
||||
totalPowerRequired += powerRequired
|
||||
}
|
||||
|
||||
if !havePowerOut {
|
||||
world.World.PowerUpdated = false
|
||||
}
|
||||
|
||||
world.World.PowerAvailable, world.World.PowerNeeded = totalPowerAvailable, totalPowerRequired
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -56,6 +56,7 @@ func (s *RenderHudSystem) Draw(_ *gohan.Context, screen *ebiten.Image) error {
|
|||
if world.World.HUDUpdated {
|
||||
s.hudImg.Clear()
|
||||
s.drawSidebar()
|
||||
s.drawMessages()
|
||||
s.drawTooltip()
|
||||
world.World.HUDUpdated = false
|
||||
}
|
||||
|
@ -67,7 +68,7 @@ func (s *RenderHudSystem) drawSidebar() {
|
|||
bounds := s.hudImg.Bounds()
|
||||
if bounds.Dx() != world.World.ScreenW || bounds.Dy() != world.World.ScreenH {
|
||||
s.hudImg = ebiten.NewImage(world.World.ScreenW, world.World.ScreenH)
|
||||
s.tmpImg = ebiten.NewImage(world.SidebarWidth, world.World.ScreenH)
|
||||
s.tmpImg = ebiten.NewImage(world.World.ScreenW, world.World.ScreenH)
|
||||
s.tmpImg2 = ebiten.NewImage(world.SidebarWidth, world.World.ScreenH)
|
||||
} else {
|
||||
s.hudImg.Clear()
|
||||
|
@ -126,6 +127,8 @@ func (s *RenderHudSystem) drawSidebar() {
|
|||
rciX := buttonWidth
|
||||
rciY := lastButtonY + buttonHeight + 55 + rciPadding
|
||||
|
||||
const rciButtonHeight = 20
|
||||
|
||||
// Draw RCI bars.
|
||||
colorR := color.RGBA{0, 255, 0, 255}
|
||||
colorC := color.RGBA{0, 0, 255, 255}
|
||||
|
@ -136,8 +139,14 @@ func (s *RenderHudSystem) drawSidebar() {
|
|||
barOffset := -barOffsetSize + (i * barOffsetSize)
|
||||
barWidth := 7
|
||||
barX := rciX + buttonWidth/2 - barWidth/2 + barOffset
|
||||
barY := rciY + (rciSize / 2)
|
||||
if demand < 0 {
|
||||
barY += rciButtonHeight / 2
|
||||
} else {
|
||||
barY -= rciButtonHeight / 2
|
||||
}
|
||||
barHeight := int((float64(rciSize) / 2) * demand)
|
||||
s.tmpImg.SubImage(image.Rect(barX, rciY+(rciSize/2), barX+barWidth, rciY+(rciSize/2)-barHeight)).(*ebiten.Image).Fill(clr)
|
||||
s.tmpImg.SubImage(image.Rect(barX, barY, barX+barWidth, barY-barHeight)).(*ebiten.Image).Fill(clr)
|
||||
}
|
||||
drawDemandBar(demandR, colorR, 0)
|
||||
drawDemandBar(demandC, colorC, 1)
|
||||
|
@ -145,7 +154,6 @@ func (s *RenderHudSystem) drawSidebar() {
|
|||
|
||||
// Draw RCI button.
|
||||
const rciButtonPadding = 12
|
||||
const rciButtonHeight = 20
|
||||
const rciButtonLabelPaddingX = 6
|
||||
const rciButtonLabelPaddingY = 1
|
||||
rciButtonY := rciY + (rciSize / 2) - (rciButtonHeight / 2)
|
||||
|
@ -208,10 +216,11 @@ func (s *RenderHudSystem) drawTooltip() {
|
|||
}
|
||||
|
||||
lines := 1 + strings.Count(label, "\n")
|
||||
max := maxLen(strings.Split(label, "\n"))
|
||||
|
||||
scale := 3.0
|
||||
x, y := world.SidebarWidth, 0
|
||||
w, h := (len(label)*6+10)*int(scale), 22*(int(scale))*lines
|
||||
w, h := (max*6+10)*int(scale), 16*(int(scale))*lines+10
|
||||
r := image.Rect(x, y, x+w, y+h)
|
||||
s.hudImg.SubImage(r).(*ebiten.Image).Fill(color.RGBA{0, 0, 0, 120})
|
||||
|
||||
|
@ -219,7 +228,55 @@ func (s *RenderHudSystem) drawTooltip() {
|
|||
ebitenutil.DebugPrint(s.tmpImg, label)
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
op.GeoM.Scale(scale, scale)
|
||||
op.GeoM.Translate(world.SidebarWidth+(4*scale), 4)
|
||||
op.GeoM.Translate(world.SidebarWidth+(4*scale), 2)
|
||||
s.hudImg.DrawImage(s.tmpImg, op)
|
||||
}
|
||||
|
||||
func maxLen(v []string) int {
|
||||
max := 0
|
||||
for _, line := range v {
|
||||
l := len(line)
|
||||
if l > max {
|
||||
max = l
|
||||
}
|
||||
}
|
||||
return max
|
||||
}
|
||||
|
||||
func (s *RenderHudSystem) drawMessages() {
|
||||
lines := len(world.World.Messages)
|
||||
if lines == 0 {
|
||||
return
|
||||
}
|
||||
/*var label string
|
||||
max := maxLen(world.World.Messages)
|
||||
for i := lines - 1; i >= 0; i-- {
|
||||
if i != lines-1 {
|
||||
label += "\n"
|
||||
}
|
||||
for j := max - len(world.World.Messages[i]); j > 0; j-- {
|
||||
label += " "
|
||||
}
|
||||
label += world.World.Messages[i]
|
||||
}*/
|
||||
|
||||
label := world.World.Messages[len(world.World.Messages)-1]
|
||||
max := len(label)
|
||||
lines = 1
|
||||
|
||||
const padding = 12
|
||||
|
||||
scale := 2.0
|
||||
w, h := (max*6+10)*int(scale), 16*(int(scale))*lines+6
|
||||
x, y := world.World.ScreenW-w, 0
|
||||
r := image.Rect(x, y, x+w, y+h)
|
||||
s.hudImg.SubImage(r).(*ebiten.Image).Fill(color.RGBA{0, 0, 0, 120})
|
||||
|
||||
s.tmpImg.Clear()
|
||||
ebitenutil.DebugPrint(s.tmpImg, label)
|
||||
op := &ebiten.DrawImageOptions{}
|
||||
op.GeoM.Scale(scale, scale)
|
||||
op.GeoM.Translate(float64(x)+padding, 0)
|
||||
s.hudImg.DrawImage(s.tmpImg, op)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,100 +0,0 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"strings"
|
||||
|
||||
"code.rocketnine.space/tslocum/citylimits/component"
|
||||
"code.rocketnine.space/tslocum/citylimits/world"
|
||||
"code.rocketnine.space/tslocum/gohan"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
||||
)
|
||||
|
||||
type RenderMessageSystem struct {
|
||||
op *ebiten.DrawImageOptions
|
||||
logoImg *ebiten.Image
|
||||
msgImg *ebiten.Image
|
||||
tmpImg *ebiten.Image
|
||||
}
|
||||
|
||||
func NewRenderMessageSystem() *RenderMessageSystem {
|
||||
s := &RenderMessageSystem{
|
||||
op: &ebiten.DrawImageOptions{},
|
||||
logoImg: ebiten.NewImage(1, 1),
|
||||
msgImg: ebiten.NewImage(1, 1),
|
||||
tmpImg: ebiten.NewImage(200, 200),
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *RenderMessageSystem) Needs() []gohan.ComponentID {
|
||||
return []gohan.ComponentID{
|
||||
component.PositionComponentID,
|
||||
component.VelocityComponentID,
|
||||
component.WeaponComponentID,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RenderMessageSystem) Uses() []gohan.ComponentID {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *RenderMessageSystem) Update(_ *gohan.Context) error {
|
||||
if !world.World.GameStarted || world.World.GameOver || !world.World.MessageVisible {
|
||||
return nil
|
||||
}
|
||||
|
||||
world.World.MessageTicks++
|
||||
if world.World.MessageTicks == world.World.MessageDuration {
|
||||
world.World.MessageVisible = false
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *RenderMessageSystem) Draw(_ *gohan.Context, screen *ebiten.Image) error {
|
||||
if !world.World.GameStarted || !world.World.MessageVisible {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Draw message.
|
||||
if world.World.MessageUpdated {
|
||||
s.drawMessage()
|
||||
}
|
||||
bounds := s.msgImg.Bounds()
|
||||
x := (float64(world.World.ScreenW) / 2) - (float64(bounds.Dx()) / 2)
|
||||
y := (float64(world.World.ScreenH) / 2) - (float64(bounds.Dy()) / 2)
|
||||
s.op.GeoM.Reset()
|
||||
s.op.GeoM.Translate(x, y)
|
||||
screen.DrawImage(s.msgImg, s.op)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *RenderMessageSystem) drawMessage() {
|
||||
split := strings.Split(world.World.MessageText, "\n")
|
||||
width := 0
|
||||
for _, line := range split {
|
||||
lineSize := len(line) * 12
|
||||
if lineSize > width {
|
||||
width = lineSize
|
||||
}
|
||||
}
|
||||
height := len(split) * 32
|
||||
|
||||
const padding = 8
|
||||
width, height = width+padding*2, height+padding*2
|
||||
|
||||
s.msgImg = ebiten.NewImage(width, height)
|
||||
s.msgImg.Fill(color.RGBA{17, 17, 17, 255})
|
||||
|
||||
s.tmpImg.Clear()
|
||||
s.tmpImg = ebiten.NewImage(width*2, height*2)
|
||||
s.op.GeoM.Reset()
|
||||
s.op.GeoM.Scale(2, 2)
|
||||
s.op.GeoM.Translate(float64(padding), float64(padding))
|
||||
ebitenutil.DebugPrint(s.tmpImg, world.World.MessageText)
|
||||
s.msgImg.DrawImage(s.tmpImg, s.op)
|
||||
s.op.ColorM.Reset()
|
||||
}
|
|
@ -38,6 +38,9 @@ func (s *TickSystem) Update(_ *gohan.Context) error {
|
|||
if world.World.Ticks%world.MonthTicks == 0 {
|
||||
world.World.HUDUpdated = true
|
||||
}
|
||||
if world.World.Ticks%144 == 0 {
|
||||
world.TickMessages()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
package world
|
||||
|
||||
import (
|
||||
"github.com/beefsack/go-astar"
|
||||
)
|
||||
|
||||
const (
|
||||
powerEmptyTile = iota
|
||||
powerSourceTile
|
||||
powerDestinationTile
|
||||
)
|
||||
|
||||
type PowerMapTile struct {
|
||||
X int
|
||||
Y int
|
||||
CarriesPower bool // Set to true for roads and all building tiles (even power plants)
|
||||
}
|
||||
|
||||
func (t *PowerMapTile) Up() *PowerMapTile {
|
||||
tx, ty := t.X, t.Y-1
|
||||
if !ValidXY(tx, ty) {
|
||||
return nil
|
||||
}
|
||||
n := World.Power[tx][ty]
|
||||
if !n.CarriesPower {
|
||||
return nil
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (t *PowerMapTile) Down() *PowerMapTile {
|
||||
tx, ty := t.X, t.Y+1
|
||||
if !ValidXY(tx, ty) {
|
||||
return nil
|
||||
}
|
||||
n := World.Power[tx][ty]
|
||||
if !n.CarriesPower {
|
||||
return nil
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (t *PowerMapTile) Left() *PowerMapTile {
|
||||
tx, ty := t.X-1, t.Y
|
||||
if !ValidXY(tx, ty) {
|
||||
return nil
|
||||
}
|
||||
n := World.Power[tx][ty]
|
||||
if !n.CarriesPower {
|
||||
return nil
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (t *PowerMapTile) Right() *PowerMapTile {
|
||||
tx, ty := t.X+1, t.Y
|
||||
if !ValidXY(tx, ty) {
|
||||
return nil
|
||||
}
|
||||
n := World.Power[tx][ty]
|
||||
if !n.CarriesPower {
|
||||
return nil
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
type PowerMap [][]*PowerMapTile
|
||||
|
||||
func newPowerMap() PowerMap {
|
||||
m := make(PowerMap, 256)
|
||||
for x := 0; x < 256; x++ {
|
||||
m[x] = make([]*PowerMapTile, 256)
|
||||
for y := 0; y < 256; y++ {
|
||||
m[x][y] = &PowerMapTile{
|
||||
X: x,
|
||||
Y: y,
|
||||
}
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func newPowerOuts() [][]bool {
|
||||
m := make([][]bool, 256)
|
||||
for x := 0; x < 256; x++ {
|
||||
m[x] = make([]bool, 256)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func ResetPowerOuts() {
|
||||
for x := 0; x < 256; x++ {
|
||||
for y := 0; y < 256; y++ {
|
||||
World.PowerOuts[x][y] = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m PowerMap) GetTile(x, y int) *PowerMapTile {
|
||||
if !ValidXY(x, y) {
|
||||
return nil
|
||||
}
|
||||
return m[x][y]
|
||||
}
|
||||
|
||||
func (m PowerMap) SetTile(x, y int, carriesPower bool) {
|
||||
t := m[x][y]
|
||||
t.CarriesPower = carriesPower
|
||||
|
||||
World.PowerUpdated = true
|
||||
}
|
||||
|
||||
func (t *PowerMapTile) PathNeighbors() []astar.Pather {
|
||||
var neighbors []astar.Pather
|
||||
n := t.Up()
|
||||
if n != nil {
|
||||
neighbors = append(neighbors, n)
|
||||
}
|
||||
n = t.Down()
|
||||
if n != nil {
|
||||
neighbors = append(neighbors, n)
|
||||
}
|
||||
n = t.Left()
|
||||
if n != nil {
|
||||
neighbors = append(neighbors, n)
|
||||
}
|
||||
n = t.Right()
|
||||
if n != nil {
|
||||
neighbors = append(neighbors, n)
|
||||
}
|
||||
return neighbors
|
||||
}
|
||||
|
||||
func (t *PowerMapTile) PathNeighborCost(to astar.Pather) float64 {
|
||||
toT := to.(*PowerMapTile)
|
||||
if !toT.CarriesPower {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func (t *PowerMapTile) PathEstimatedCost(to astar.Pather) float64 {
|
||||
toT := to.(*PowerMapTile)
|
||||
absX := toT.X - t.X
|
||||
if absX < 0 {
|
||||
absX = -absX
|
||||
}
|
||||
absY := toT.Y - t.Y
|
||||
if absY < 0 {
|
||||
absY = -absY
|
||||
}
|
||||
return float64(absX + absY)
|
||||
}
|
228
world/world.go
228
world/world.go
|
@ -5,9 +5,11 @@ import (
|
|||
"fmt"
|
||||
"image"
|
||||
"log"
|
||||
"math"
|
||||
"math/rand"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
"golang.org/x/text/message"
|
||||
|
@ -22,6 +24,8 @@ import (
|
|||
|
||||
const startingYear = 1950
|
||||
|
||||
const maxPopulation = 100000
|
||||
|
||||
const (
|
||||
MonthTicks = 144 * 3
|
||||
YearTicks = MonthTicks * 12
|
||||
|
@ -59,12 +63,20 @@ var World = &GameWorld{
|
|||
ResetGame: true,
|
||||
Level: NewLevel(256),
|
||||
Printer: message.NewPrinter(language.English),
|
||||
Power: newPowerMap(),
|
||||
PowerOuts: newPowerOuts(),
|
||||
}
|
||||
|
||||
type Zone struct {
|
||||
Type int // StructureResidentialZone, StructureCommercialZone or StructureIndustrialZone
|
||||
X, Y int
|
||||
Population int
|
||||
Powered bool
|
||||
}
|
||||
|
||||
type PowerPlant struct {
|
||||
Type int
|
||||
X, Y int
|
||||
}
|
||||
|
||||
type GameWorld struct {
|
||||
|
@ -83,12 +95,6 @@ type GameWorld struct {
|
|||
GameStartedTicks int
|
||||
GameOver bool
|
||||
|
||||
MessageVisible bool
|
||||
MessageTicks int
|
||||
MessageDuration int
|
||||
MessageUpdated bool
|
||||
MessageText string
|
||||
|
||||
PlayerX, PlayerY float64
|
||||
|
||||
CamX, CamY float64
|
||||
|
@ -136,7 +142,10 @@ type GameWorld struct {
|
|||
HUDUpdated bool
|
||||
HUDButtonRects []image.Rectangle
|
||||
|
||||
Zones []*Zone
|
||||
PowerPlants []*PowerPlant
|
||||
Zones []*Zone
|
||||
|
||||
PowerOuts [][]bool
|
||||
|
||||
Ticks int
|
||||
|
||||
|
@ -148,6 +157,14 @@ type GameWorld struct {
|
|||
|
||||
TransparentStructures bool
|
||||
|
||||
Messages []string
|
||||
MessagesTicks []int
|
||||
|
||||
Power PowerMap
|
||||
PowerUpdated bool
|
||||
PowerAvailable int
|
||||
PowerNeeded int
|
||||
|
||||
resetTipShown bool
|
||||
}
|
||||
|
||||
|
@ -171,8 +188,6 @@ func Reset() {
|
|||
World.TriggerEntities = nil
|
||||
World.TriggerRects = nil
|
||||
World.TriggerNames = nil
|
||||
|
||||
World.MessageVisible = false
|
||||
}
|
||||
|
||||
func LoadMap(structureType int) (*tiled.Map, error) {
|
||||
|
@ -269,32 +284,6 @@ func LoadTileset() error {
|
|||
}
|
||||
|
||||
func BuildStructure(structureType int, hover bool, placeX int, placeY int) (*Structure, error) {
|
||||
if structureType == StructureBulldozer && !hover {
|
||||
// TODO bulldoze entire structure, remove from zones
|
||||
var bulldozed bool
|
||||
for i := range World.Level.Tiles {
|
||||
if World.Level.Tiles[i][placeX][placeY].Sprite != nil {
|
||||
World.Level.Tiles[i][placeX][placeY].Sprite = nil
|
||||
bulldozed = true
|
||||
}
|
||||
|
||||
var img *ebiten.Image
|
||||
if i == 0 {
|
||||
img = World.TileImages[DirtTile+World.TileImagesFirstGID]
|
||||
}
|
||||
if World.Level.Tiles[i][placeX][placeY].EnvironmentSprite != img {
|
||||
World.Level.Tiles[i][placeX][placeY].EnvironmentSprite = img
|
||||
bulldozed = true
|
||||
}
|
||||
}
|
||||
if !bulldozed {
|
||||
return nil, errors.New("nothing to bulldoze")
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
initialType := structureType
|
||||
|
||||
// For previewing buildings
|
||||
/*v := rand.Intn(3)
|
||||
if structureType == StructureResidentialZone {
|
||||
|
@ -343,10 +332,41 @@ func BuildStructure(structureType int, hover bool, placeX int, placeY int) (*Str
|
|||
w := m.Width - 1
|
||||
h := m.Height - 1
|
||||
|
||||
if placeX-w < 0 || placeY-h < 0 || placeX > 256 || placeY > 256 {
|
||||
if placeX-w < 0 || placeY-h < 0 || placeX >= 256 || placeY >= 256 {
|
||||
return nil, errors.New("invalid location: building does not fit")
|
||||
}
|
||||
|
||||
structure := &Structure{
|
||||
Type: structureType,
|
||||
X: placeX,
|
||||
Y: placeY,
|
||||
}
|
||||
|
||||
if structureType == StructureBulldozer && !hover {
|
||||
// TODO bulldoze entire structure, remove from zones
|
||||
var bulldozed bool
|
||||
for i := range World.Level.Tiles {
|
||||
if World.Level.Tiles[i][placeX][placeY].Sprite != nil {
|
||||
World.Level.Tiles[i][placeX][placeY].Sprite = nil
|
||||
bulldozed = true
|
||||
}
|
||||
|
||||
var img *ebiten.Image
|
||||
if i == 0 {
|
||||
img = World.TileImages[DirtTile+World.TileImagesFirstGID]
|
||||
}
|
||||
if World.Level.Tiles[i][placeX][placeY].EnvironmentSprite != img {
|
||||
World.Level.Tiles[i][placeX][placeY].EnvironmentSprite = img
|
||||
bulldozed = true
|
||||
}
|
||||
}
|
||||
if !bulldozed {
|
||||
return nil, errors.New("nothing to bulldoze")
|
||||
}
|
||||
World.Power.SetTile(placeX, placeY, false)
|
||||
return structure, nil
|
||||
}
|
||||
|
||||
createTileEntity := func(t *tiled.LayerTile, x float64, y float64) gohan.Entity {
|
||||
mapTile := ECS.NewEntity()
|
||||
ECS.AddComponent(mapTile, &component.PositionComponent{
|
||||
|
@ -366,12 +386,6 @@ func BuildStructure(structureType int, hover bool, placeX int, placeY int) (*Str
|
|||
}
|
||||
_ = createTileEntity
|
||||
|
||||
structure := &Structure{
|
||||
Type: structureType,
|
||||
X: placeX,
|
||||
Y: placeY,
|
||||
}
|
||||
|
||||
// TODO Add entity
|
||||
|
||||
tileOccupied := func(tx int, ty int) bool {
|
||||
|
@ -457,6 +471,11 @@ VALIDBUILD:
|
|||
}
|
||||
} else {
|
||||
World.Level.Tiles[layerNum][tx][ty].Sprite = World.TileImages[t.Tileset.FirstGID+t.ID]
|
||||
|
||||
if structureType == StructureRoad {
|
||||
World.Power.SetTile(tx, ty, true)
|
||||
}
|
||||
World.PowerUpdated = true
|
||||
}
|
||||
|
||||
// TODO handle flipping
|
||||
|
@ -464,16 +483,6 @@ VALIDBUILD:
|
|||
}
|
||||
}
|
||||
|
||||
isZone := initialType == StructureResidentialZone || initialType == StructureCommercialZone || initialType == StructureIndustrialZone
|
||||
if !hover && isZone {
|
||||
zone := &Zone{
|
||||
Type: initialType,
|
||||
X: placeX,
|
||||
Y: placeY,
|
||||
}
|
||||
World.Zones = append(World.Zones, zone)
|
||||
}
|
||||
|
||||
return structure, nil
|
||||
}
|
||||
|
||||
|
@ -528,14 +537,6 @@ func StartGame() {
|
|||
}
|
||||
}
|
||||
|
||||
func SetMessage(message string, duration int) {
|
||||
World.MessageText = message
|
||||
World.MessageVisible = true
|
||||
World.MessageUpdated = true
|
||||
World.MessageDuration = duration
|
||||
World.MessageTicks = 0
|
||||
}
|
||||
|
||||
// CartesianToIso transforms cartesian coordinates into isometric coordinates.
|
||||
func CartesianToIso(x, y float64) (float64, float64) {
|
||||
ix := (x - y) * float64(TileSize/2)
|
||||
|
@ -584,14 +585,46 @@ func SetHoverStructure(structureType int) {
|
|||
World.HUDUpdated = true
|
||||
}
|
||||
|
||||
func Demand() (r, c, i float64) {
|
||||
r = (rand.Float64() * 2) - 1
|
||||
c = (rand.Float64() * 2) - 1
|
||||
i = (rand.Float64() * 2) - 1
|
||||
return r, c, i
|
||||
func Satisfaction() (r, c, i float64) {
|
||||
return 0.01, 0.0, 0.02
|
||||
}
|
||||
|
||||
var structureTooltips = map[int]string{
|
||||
func TargetPopulation() (r, c, i int) {
|
||||
currentMax := maxPopulation * ((1 + float64(World.Ticks/(MonthTicks*7))) / 108)
|
||||
|
||||
satisfactionR, satisfactionC, satisfactionI := Satisfaction()
|
||||
return int(satisfactionR * currentMax), int(satisfactionC * currentMax), int(satisfactionI * currentMax)
|
||||
}
|
||||
|
||||
func Demand() (r, c, i float64) {
|
||||
targetR, targetC, targetI := TargetPopulation()
|
||||
|
||||
populationR, populationC, populationI := Population()
|
||||
r, c, i = float64(targetR)-float64(populationR), float64(targetC)-float64(populationC), float64(targetI)-float64(populationI)
|
||||
max := r
|
||||
if c > max {
|
||||
max = c
|
||||
}
|
||||
if i > max {
|
||||
max = i
|
||||
}
|
||||
barPeak := 100.0
|
||||
r, c, i = r/barPeak, c/barPeak, i/barPeak
|
||||
clamp := func(v float64) float64 {
|
||||
if math.IsNaN(v) {
|
||||
return 0
|
||||
}
|
||||
if v < -1 {
|
||||
v = -1
|
||||
} else if v > 1 {
|
||||
v = 1
|
||||
}
|
||||
return v
|
||||
}
|
||||
return clamp(r), clamp(c), clamp(i)
|
||||
}
|
||||
|
||||
var StructureTooltips = map[int]string{
|
||||
StructureToggleTransparentStructures: "Transparent buildings",
|
||||
StructureBulldozer: "Bulldozer",
|
||||
StructureRoad: "Road",
|
||||
|
@ -613,7 +646,7 @@ var StructureCosts = map[int]int{
|
|||
}
|
||||
|
||||
func Tooltip() string {
|
||||
tooltipText := structureTooltips[World.HoverStructure]
|
||||
tooltipText := StructureTooltips[World.HoverStructure]
|
||||
cost := StructureCosts[World.HoverStructure]
|
||||
if cost > 0 {
|
||||
tooltipText += World.Printer.Sprintf("\n$%d", cost)
|
||||
|
@ -640,3 +673,64 @@ func Date() (month string, year string) {
|
|||
y, m := World.Ticks/YearTicks, (World.Ticks%YearTicks)/MonthTicks
|
||||
return monthNames[m], strconv.Itoa(startingYear + y)
|
||||
}
|
||||
|
||||
func Population() (r, c, i int) {
|
||||
for _, zone := range World.Zones {
|
||||
switch zone.Type {
|
||||
case StructureResidentialZone:
|
||||
r += zone.Population
|
||||
case StructureCommercialZone:
|
||||
c += zone.Population
|
||||
case StructureIndustrialZone:
|
||||
i += zone.Population
|
||||
}
|
||||
}
|
||||
return r, c, i
|
||||
}
|
||||
|
||||
var messageLock = &sync.Mutex{}
|
||||
|
||||
const messageDuration = 144 * 3
|
||||
|
||||
func TickMessages() {
|
||||
messageLock.Lock()
|
||||
defer messageLock.Unlock()
|
||||
|
||||
var removed int
|
||||
for j := 0; j < len(World.MessagesTicks); j++ {
|
||||
i := j - removed
|
||||
if World.MessagesTicks[i] == 0 {
|
||||
World.Messages = append(World.Messages[:i], World.Messages[i+1:]...)
|
||||
World.MessagesTicks = append(World.MessagesTicks[:i], World.MessagesTicks[i+1:]...)
|
||||
removed++
|
||||
|
||||
World.HUDUpdated = true
|
||||
} else if World.MessagesTicks[i] > 0 {
|
||||
World.MessagesTicks[i]--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ShowMessage(message string, duration int) {
|
||||
messageLock.Lock()
|
||||
defer messageLock.Unlock()
|
||||
|
||||
World.Messages = append(World.Messages, message)
|
||||
World.MessagesTicks = append(World.MessagesTicks, duration)
|
||||
|
||||
World.HUDUpdated = true
|
||||
}
|
||||
|
||||
func ValidXY(x, y int) bool {
|
||||
return x >= 0 && y >= 0 && x < 256 && y < 256
|
||||
}
|
||||
|
||||
var PowerPlantCapacities = map[int]int{
|
||||
StructurePowerPlantCoal: 60,
|
||||
}
|
||||
|
||||
var ZonePowerRequirement = map[int]int{
|
||||
StructureResidentialZone: 1,
|
||||
StructureCommercialZone: 1,
|
||||
StructureIndustrialZone: 1,
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue