From cabff986f4572c34f2f5e506e373ae6261e939d7 Mon Sep 17 00:00:00 2001 From: Trevor Slocum Date: Sun, 23 Jan 2022 00:13:55 -0800 Subject: [PATCH] Add RCI window --- asset/asset.go | 2 +- game/game.go | 5 +- system/input_move.go | 15 ++++-- system/renderhud.go | 62 +++++++++++++++++++++-- system/tax.go | 15 ++++-- world/help.go | 28 +++++------ world/world.go | 114 ++++++++++++++++++++++++++++++++++++------- 7 files changed, 196 insertions(+), 45 deletions(-) diff --git a/asset/asset.go b/asset/asset.go index a60fdba..d05e000 100644 --- a/asset/asset.go +++ b/asset/asset.go @@ -51,7 +51,7 @@ func LoadSounds(ctx *audio.Context) { SoundSelect.SetVolume(0.6) SoundBulldoze = LoadOGG(ctx, "sound/bulldozer/bulldozer.ogg", true) - SoundBulldoze.SetVolume(0.4) + SoundBulldoze.SetVolume(0.6) const popVolume = 0.15 SoundPop1 = LoadWAV(ctx, "sound/pop/pop1.wav") diff --git a/game/game.go b/game/game.go index 15bc11c..5ad031b 100644 --- a/game/game.go +++ b/game/game.go @@ -135,13 +135,14 @@ func (g *game) Update() error { Sprite: world.DrawMap(world.StructureBulldozer), SpriteOffsetX: 12, SpriteOffsetY: -48, - }, { + }, + nil, + { StructureType: world.StructureRoad, Sprite: world.DrawMap(world.StructureRoad), SpriteOffsetX: -12, SpriteOffsetY: -28, }, - nil, { StructureType: world.StructureResidentialZone, Sprite: world.DrawMap(world.StructureResidentialLow), diff --git a/system/input_move.go b/system/input_move.go index a33f3d3..bd9b4a1 100644 --- a/system/input_move.go +++ b/system/input_move.go @@ -58,7 +58,7 @@ func (s *playerMoveSystem) buildStructure(structureType int, tileX int, tileY in } structure, err := world.BuildStructure(world.World.HoverStructure, false, tileX, tileY) - if err == nil { + if err == nil || world.World.HoverStructure == world.StructureBulldozer { world.World.LastBuildX, world.World.LastBuildY = tileX, tileY if world.IsPowerPlant(world.World.HoverStructure) { @@ -89,8 +89,9 @@ func (s *playerMoveSystem) buildStructure(structureType int, tileX int, tileY in sound.Play() } - cost := world.StructureCosts[structureType] - world.World.Funds -= cost + if err == nil { + world.World.Funds -= cost + } world.World.HUDUpdated = true } else { @@ -297,11 +298,19 @@ func (s *playerMoveSystem) Update(ctx *gohan.Context) error { asset.SoundSelect.Rewind() asset.SoundSelect.Play() } + } else if world.AltButtonAt(x, y) == 0 { + world.World.ShowRCIWindow = !world.World.ShowRCIWindow + world.World.HUDUpdated = true + + asset.SoundSelect.Rewind() + asset.SoundSelect.Play() } } return nil } + world.HandleRCIWindowClick(x, y) + if x >= world.World.ScreenW-helpW && y >= world.World.ScreenH-helpH { if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) { const ( diff --git a/system/renderhud.go b/system/renderhud.go index d99e4eb..7e99ce9 100644 --- a/system/renderhud.go +++ b/system/renderhud.go @@ -1,9 +1,11 @@ package system import ( + "fmt" "image" "image/color" "math" + "strconv" "strings" "github.com/hajimehoshi/ebiten/v2/ebitenutil" @@ -16,7 +18,7 @@ import ( const ( helpW = 480 - helpH = 225 + helpH = 220 ) type RenderHudSystem struct { @@ -37,7 +39,7 @@ func NewRenderHudSystem() *RenderHudSystem { helpImg: ebiten.NewImage(helpW, helpH), } - sidebarShade := uint8(111) + sidebarShade := uint8(108) s.sidebarColor = color.RGBA{sidebarShade, sidebarShade, sidebarShade, 255} return s @@ -66,6 +68,7 @@ func (s *RenderHudSystem) Draw(_ *gohan.Context, screen *ebiten.Image) error { s.drawSidebar() s.drawMessages() s.drawTooltip() + s.drawRCIWindow() s.drawHelp() world.World.HUDUpdated = false } @@ -261,12 +264,14 @@ func (s *RenderHudSystem) drawDemand(x, y int) { rciButtonY := rciY + (rciSize / 2) - (rciButtonHeight / 2) rciButtonRect := image.Rect(rciX+rciButtonPadding, rciButtonY, rciX+buttonWidth-rciButtonPadding, rciButtonY+rciButtonHeight) - s.drawButtonBackground(s.tmpImg, rciButtonRect, false) // TODO + s.drawButtonBackground(s.tmpImg, rciButtonRect, world.World.ShowRCIWindow) // Draw label. ebitenutil.DebugPrintAt(s.tmpImg, "R C I", rciX+rciButtonPadding+rciButtonLabelPaddingX, rciButtonY+rciButtonLabelPaddingY) - s.drawButtonBorder(s.tmpImg, rciButtonRect, false) // TODO + s.drawButtonBorder(s.tmpImg, rciButtonRect, world.World.ShowRCIWindow) + + world.World.RCIButtonRect = rciButtonRect } func (s *RenderHudSystem) drawPower(x, y int) { @@ -468,3 +473,52 @@ func (s *RenderHudSystem) drawHelp() { op.GeoM.Translate(float64(world.World.ScreenW)-helpW, float64(world.World.ScreenH)-helpH) s.hudImg.DrawImage(s.helpImg, op) } + +func (s *RenderHudSystem) drawRCIWindow() { + if !world.World.ShowRCIWindow { + return + } + + const paddingX = 8 + + const ( + rciWindowW = 425 + rciWindowH = 100 + ) + + rciWindowRect := image.Rect(world.World.ScreenW/2-rciWindowW/2, world.World.ScreenH/2-rciWindowH/2, world.World.ScreenW/2+rciWindowW, world.World.ScreenH/2+rciWindowH) + s.hudImg.SubImage(rciWindowRect).(*ebiten.Image).Fill(s.sidebarColor) + + percentBar := func(tax float64) string { + if tax >= 1.0 { + tax = .99 + } + bar := "----------" + bar = bar[:int(tax*10)] + "%" + bar[int(tax*10)+1:] + return bar + } + + label := fmt.Sprintf(` +Residential %3s%% - |%s| + +Commercial %3s%% - |%s| + +Industrial %3s%% - |%s| + +`, + strconv.Itoa(int(world.World.TaxR*100)), percentBar(world.World.TaxR), + strconv.Itoa(int(world.World.TaxC*100)), percentBar(world.World.TaxC), + strconv.Itoa(int(world.World.TaxI*100)), percentBar(world.World.TaxI)) + + s.tmpImg.Clear() + ebitenutil.DebugPrint(s.tmpImg, strings.TrimSpace(label)) + + op := &ebiten.DrawImageOptions{} + op.GeoM.Scale(3, 3) + op.GeoM.Translate(float64(rciWindowRect.Min.X)+paddingX, float64(rciWindowRect.Min.Y)) + s.hudImg.DrawImage(s.tmpImg, op) + + s.hudImg.SubImage(image.Rect(rciWindowRect.Min.X, rciWindowRect.Min.Y, rciWindowRect.Max.X, rciWindowRect.Min.Y+1)).(*ebiten.Image).Fill(color.Black) + s.hudImg.SubImage(image.Rect(rciWindowRect.Min.X, rciWindowRect.Max.Y-1, rciWindowRect.Max.X, rciWindowRect.Max.Y)).(*ebiten.Image).Fill(color.Black) + s.hudImg.SubImage(image.Rect(rciWindowRect.Min.X, rciWindowRect.Min.Y, rciWindowRect.Min.X+1, rciWindowRect.Max.Y)).(*ebiten.Image).Fill(color.Black) + s.hudImg.SubImage(image.Rect(rciWindowRect.Max.X-1, rciWindowRect.Min.Y, rciWindowRect.Max.X, rciWindowRect.Max.Y)).(*ebiten.Image).Fill(color.Black) + + world.World.RCIWindowRect = rciWindowRect +} diff --git a/system/tax.go b/system/tax.go index a07eaf4..46bc403 100644 --- a/system/tax.go +++ b/system/tax.go @@ -37,15 +37,24 @@ func (s *TaxSystem) Update(_ *gohan.Context) error { return nil } + taxCollectionAmount := 777.77 for _, zone := range world.World.Zones { - if !zone.Powered { + if zone.Population == 0 { continue } - _ = zone + + taxRate := world.World.TaxR + if zone.Type == world.StructureCommercialZone { + taxRate = world.World.TaxC + } else if zone.Type == world.StructureIndustrialZone { + taxRate = world.World.TaxI + } + + world.World.Funds += int(taxCollectionAmount * taxRate) } return nil } -func (s *TaxSystem) Draw(ctx *gohan.Context, screen *ebiten.Image) error { +func (s *TaxSystem) Draw(_ *gohan.Context, _ *ebiten.Image) error { return gohan.ErrSystemWithoutDraw } diff --git a/world/help.go b/world/help.go index f85ed66..1c02bed 100644 --- a/world/help.go +++ b/world/help.go @@ -8,55 +8,55 @@ things YOUR way. For better or worse... Will you lead the clean energy front, or will you put profits before people? `, ` -Moving via mouse (2/10) +Moving Via Mouse (2/10) To move around, place your cursor at the edge of the screen, or press and hold your middle mouse button while moving your cursor. `, ` -Moving via keyboard (3/10) +Moving Via Keyboard (3/10) You can also use your keyboard to move by pressing any of the arrow keys or W/A/S/D. Try using your mouse and/or keyboard to move the camera around now. `, ` -Zoning areas (4/10) -Structures are built according to the -zoning category of an area. Demand for -each category (Residential, Commercial, +Zoning Areas (4/10) +Structures are built according to how +you zone areas of land. Demand for each +category (Residential, Commercial, Industrial) is shown in the sidebar. `, ` -Did you know? (5/10) +Did You Know? (5/10) Powering an area requires more input energy than the resulting electricity. -In in fact, only a third of the energy -is transmitted as usable electricity. +In fact, only a third of the energy is +transmitted as usable electricity. `, ` -Building blocks of a city (6/10) +Building Blocks of a City (6/10) Structures require power, sewer and transportation in order to function. This is all facilitated via roads and underground wiring and piping. `, ` -Did you know? (7/10) +Did You Know? (7/10) Meat-based diets require much more energy, land and water resources than vegetarian and especially vegan diets. Animal farms are a source of pollution. `, ` -Power to the people (8/10) +Power to the People (8/10) Build a power plant, then zone a few areas for residential development. Connect the power plant to the newly zoned areas with a road. `, ` -Did you know? (9/10) +Did You Know? (9/10) It takes large amounts of water to convert fossil fuels into electricity. Converting solar and wind energy uses negligible amounts of water. `, ` -Collecting taxes (10/10) +Collecting Taxes (10/10) Each year, taxes are collected from the residents of the city. The tax rate you set determines how appealing your city diff --git a/world/world.go b/world/world.go index b885e63..a4eb2a3 100644 --- a/world/world.go +++ b/world/world.go @@ -13,6 +13,8 @@ import ( "sync" "time" + "github.com/hajimehoshi/ebiten/v2/inpututil" + "github.com/hajimehoshi/ebiten/v2/audio" "golang.org/x/text/language" @@ -31,7 +33,7 @@ const startingYear = 1950 const maxPopulation = 100000 const ( - MonthTicks = 144 * 7 + MonthTicks = 144 * 5 YearTicks = MonthTicks * 12 ) @@ -63,28 +65,33 @@ var HUDButtons []*HUDButton var CameraMinZoom = 0.1 var CameraMaxZoom = 1.0 -const ( - startingTaxR = 0.12 - startingTaxC = 0.12 - startingTaxI = 0.12 -) +const startingTax = 0.12 var World = &GameWorld{ CamScale: startingZoom, CamScaleTarget: startingZoom, CamMoving: true, - PlayerWidth: 8, - PlayerHeight: 32, - TileImages: make(map[uint32]*ebiten.Image), - ResetGame: true, - Level: NewLevel(256), - Printer: message.NewPrinter(language.English), - Power: newPowerMap(), - PowerOuts: newPowerOuts(), - BuildDragX: -1, - BuildDragY: -1, - LastBuildX: -1, - LastBuildY: -1, + + PlayerWidth: 8, + PlayerHeight: 32, + + TileImages: make(map[uint32]*ebiten.Image), + ResetGame: true, + Level: NewLevel(256), + + Power: newPowerMap(), + PowerOuts: newPowerOuts(), + + TaxR: startingTax, + TaxC: startingTax, + TaxI: startingTax, + + BuildDragX: -1, + BuildDragY: -1, + LastBuildX: -1, + LastBuildY: -1, + + Printer: message.NewPrinter(language.English), } type Zone struct { @@ -162,6 +169,10 @@ type GameWorld struct { HUDUpdated bool HUDButtonRects []image.Rectangle + RCIButtonRect image.Rectangle + RCIWindowRect image.Rectangle + ShowRCIWindow bool + HelpUpdated bool HelpPage int HelpButtonRects []image.Rectangle @@ -608,6 +619,72 @@ func HelpButtonAt(x, y int) int { return -1 } +func AltButtonAt(x, y int) int { + point := image.Point{x, y} + if point.In(World.RCIButtonRect) { + return 0 + } + return -1 +} + +func HandleRCIWindowClick(x, y int) { + if !World.ShowRCIWindow { + return + } + + if !ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) { + return + } + + point := image.Point{x, y} + if !point.In(World.RCIWindowRect) { + return + } + + var updated bool + barRectR := image.Rect(World.RCIWindowRect.Min.X+381, World.RCIWindowRect.Min.Y, World.RCIWindowRect.Min.X+575, World.RCIWindowRect.Min.Y+50) + barRectC := image.Rect(World.RCIWindowRect.Min.X+381, World.RCIWindowRect.Min.Y+50, World.RCIWindowRect.Min.X+575, World.RCIWindowRect.Min.Y+100) + barRectI := image.Rect(World.RCIWindowRect.Min.X+381, World.RCIWindowRect.Min.Y+100, World.RCIWindowRect.Min.X+575, World.RCIWindowRect.Max.Y) + if point.In(barRectR) { + World.TaxR = float64(x-barRectR.Min.X) / float64(barRectR.Dx()) + if World.TaxR >= .99 { + World.TaxR = 1.0 + } + World.HUDUpdated = true + updated = true + } else if point.In(barRectC) { + World.TaxC = float64(x-barRectC.Min.X) / float64(barRectC.Dx()) + if World.TaxC >= .99 { + World.TaxC = 1.0 + } + World.HUDUpdated = true + updated = true + } else if point.In(barRectI) { + World.TaxI = float64(x-barRectI.Min.X) / float64(barRectI.Dx()) + if World.TaxI >= .99 { + World.TaxI = 1.0 + } + World.HUDUpdated = true + updated = true + } + if !updated { + return + } + + if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) || World.Ticks%16 == 0 { + sounds := []*audio.Player{ + asset.SoundPop1, + asset.SoundPop2, + asset.SoundPop3, + asset.SoundPop4, + asset.SoundPop5, + } + sound := sounds[rand.Intn(len(sounds))] + sound.Rewind() + sound.Play() + } +} + func SetHoverStructure(structureType int) { World.HoverStructure = structureType World.HUDUpdated = true @@ -643,6 +720,7 @@ func Demand() (r, c, i float64) { } barPeak := 100.0 r, c, i = r/barPeak, c/barPeak, i/barPeak + r, c, i = r*(1-World.TaxR), c*(1-World.TaxC), i*(1-World.TaxI) clamp := func(v float64) float64 { if math.IsNaN(v) { return 0