|
|
|
@ -10,6 +10,7 @@ import (
@@ -10,6 +10,7 @@ import (
|
|
|
|
|
"os" |
|
|
|
|
"path" |
|
|
|
|
"runtime/pprof" |
|
|
|
|
"sync" |
|
|
|
|
"time" |
|
|
|
|
|
|
|
|
|
"github.com/hajimehoshi/ebiten/v2" |
|
|
|
@ -116,7 +117,8 @@ type game struct {
@@ -116,7 +117,8 @@ type game struct {
|
|
|
|
|
flashMessageText string |
|
|
|
|
flashMessageUntil time.Time |
|
|
|
|
|
|
|
|
|
forceColorScale float64 |
|
|
|
|
minLevelColorScale float64 |
|
|
|
|
minPlayerColorScale float64 |
|
|
|
|
|
|
|
|
|
godMode bool |
|
|
|
|
noclipMode bool |
|
|
|
@ -124,6 +126,8 @@ type game struct {
@@ -124,6 +126,8 @@ type game struct {
|
|
|
|
|
debugMode bool |
|
|
|
|
fullBrightMode bool |
|
|
|
|
cpuProfile *os.File |
|
|
|
|
|
|
|
|
|
sync.Mutex |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const sampleRate = 44100 |
|
|
|
@ -131,13 +135,14 @@ const sampleRate = 44100
@@ -131,13 +135,14 @@ const sampleRate = 44100
|
|
|
|
|
// NewGame returns a new isometric demo game.
|
|
|
|
|
func NewGame() (*game, error) { |
|
|
|
|
g := &game{ |
|
|
|
|
camScale: 2, |
|
|
|
|
camScaleTo: 2, |
|
|
|
|
mousePanX: math.MinInt32, |
|
|
|
|
mousePanY: math.MinInt32, |
|
|
|
|
activeGamepad: -1, |
|
|
|
|
forceColorScale: -1, |
|
|
|
|
levelNum: 1, |
|
|
|
|
camScale: 2, |
|
|
|
|
camScaleTo: 2, |
|
|
|
|
mousePanX: math.MinInt32, |
|
|
|
|
mousePanY: math.MinInt32, |
|
|
|
|
activeGamepad: -1, |
|
|
|
|
minLevelColorScale: -1, |
|
|
|
|
minPlayerColorScale: -1, |
|
|
|
|
levelNum: 1, |
|
|
|
|
|
|
|
|
|
op: &ebiten.DrawImageOptions{}, |
|
|
|
|
} |
|
|
|
@ -286,7 +291,8 @@ func (g *game) reset() error {
@@ -286,7 +291,8 @@ func (g *game) reset() error {
|
|
|
|
|
|
|
|
|
|
g.updateCursor() |
|
|
|
|
|
|
|
|
|
g.forceColorScale = -1 |
|
|
|
|
g.minLevelColorScale = -1 |
|
|
|
|
g.minPlayerColorScale = -1 |
|
|
|
|
|
|
|
|
|
g.player.hasTorch = true |
|
|
|
|
g.player.weapon = weaponUzi |
|
|
|
@ -359,10 +365,10 @@ func (g *game) handlePlayerDeath() {
@@ -359,10 +365,10 @@ func (g *game) handlePlayerDeath() {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (g *game) checkLevelComplete() { |
|
|
|
|
if g.player.soulsRescued < g.level.requiredSouls || g.level.exitOpen { |
|
|
|
|
if g.player.soulsRescued < g.level.requiredSouls || !g.level.exitOpenTime.IsZero() { |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
g.level.exitOpen = true |
|
|
|
|
g.level.exitOpenTime = time.Now() |
|
|
|
|
|
|
|
|
|
g.level.tiles[g.level.exitY][g.level.exitX].sprites = nil |
|
|
|
|
g.level.tiles[g.level.exitY][g.level.exitX].AddSprite(sandstoneSS.FloorA) |
|
|
|
@ -374,6 +380,9 @@ func (g *game) checkLevelComplete() {
@@ -374,6 +380,9 @@ func (g *game) checkLevelComplete() {
|
|
|
|
|
|
|
|
|
|
// Update reads current user input and updates the game state.
|
|
|
|
|
func (g *game) Update() error { |
|
|
|
|
g.Lock() |
|
|
|
|
defer g.Unlock() |
|
|
|
|
|
|
|
|
|
gamepadDeadZone := 0.1 |
|
|
|
|
|
|
|
|
|
if ebiten.IsKeyPressed(ebiten.KeyEscape) || ebiten.IsWindowBeingClosed() { |
|
|
|
@ -494,13 +503,6 @@ func (g *game) Update() error {
@@ -494,13 +503,6 @@ func (g *game) Update() error {
|
|
|
|
|
} |
|
|
|
|
g.level.liveCreeps = liveCreeps |
|
|
|
|
|
|
|
|
|
// Clamp target zoom level.
|
|
|
|
|
/*if g.camScaleTo < 2 { |
|
|
|
|
g.camScaleTo = 2 |
|
|
|
|
} else if g.camScaleTo > 4 { |
|
|
|
|
g.camScaleTo = 4 |
|
|
|
|
} TODO */ |
|
|
|
|
|
|
|
|
|
// Update target zoom level.
|
|
|
|
|
if g.debugMode { |
|
|
|
|
var scrollY float64 |
|
|
|
@ -883,6 +885,9 @@ func (g *game) drawText(target *ebiten.Image, offsetX float64, y float64, scale
@@ -883,6 +885,9 @@ func (g *game) drawText(target *ebiten.Image, offsetX float64, y float64, scale
|
|
|
|
|
|
|
|
|
|
// Draw draws the game on the screen.
|
|
|
|
|
func (g *game) Draw(screen *ebiten.Image) { |
|
|
|
|
g.Lock() |
|
|
|
|
defer g.Unlock() |
|
|
|
|
|
|
|
|
|
if g.gameStartTime.IsZero() { |
|
|
|
|
screen.Fill(colorBlood) |
|
|
|
|
|
|
|
|
@ -915,18 +920,22 @@ func (g *game) Draw(screen *ebiten.Image) {
@@ -915,18 +920,22 @@ func (g *game) Draw(screen *ebiten.Image) {
|
|
|
|
|
} |
|
|
|
|
drawn = g.renderLevel(screen) |
|
|
|
|
} else { |
|
|
|
|
drawn += g.drawProjectiles(screen) |
|
|
|
|
|
|
|
|
|
drawn += g.drawPlayer(screen) |
|
|
|
|
|
|
|
|
|
// Draw game over screen.
|
|
|
|
|
img := ebiten.NewImage(g.w, g.h) |
|
|
|
|
img.Fill(colorBlood) |
|
|
|
|
|
|
|
|
|
a := g.forceColorScale |
|
|
|
|
a := g.minLevelColorScale |
|
|
|
|
if a == -1 { |
|
|
|
|
a = 1 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
g.op.GeoM.Reset() |
|
|
|
|
g.op.ColorM.Reset() |
|
|
|
|
g.op.ColorM.Scale(a, a, a, 1) |
|
|
|
|
g.op.ColorM.Scale(a, a, a, a) |
|
|
|
|
screen.DrawImage(img, g.op) |
|
|
|
|
g.op.ColorM.Reset() |
|
|
|
|
|
|
|
|
@ -935,7 +944,6 @@ func (g *game) Draw(screen *ebiten.Image) {
@@ -935,7 +944,6 @@ func (g *game) Draw(screen *ebiten.Image) {
|
|
|
|
|
if time.Since(g.gameOverTime).Milliseconds()%2000 < 1500 { |
|
|
|
|
g.drawText(screen, 0, 8, 4, a, "PRESS ENTER OR START TO PLAY AGAIN") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if g.gameOverTime.IsZero() { |
|
|
|
@ -959,7 +967,7 @@ func (g *game) Draw(screen *ebiten.Image) {
@@ -959,7 +967,7 @@ func (g *game) Draw(screen *ebiten.Image) {
|
|
|
|
|
|
|
|
|
|
scale := 3.0 |
|
|
|
|
soulsY := 104.0 |
|
|
|
|
if !g.level.exitOpen { |
|
|
|
|
if g.level.exitOpenTime.IsZero() { |
|
|
|
|
// Draw souls.
|
|
|
|
|
soulsLabel := fmt.Sprintf("%d", g.level.requiredSouls-g.player.soulsRescued) |
|
|
|
|
|
|
|
|
@ -970,8 +978,9 @@ func (g *game) Draw(screen *ebiten.Image) {
@@ -970,8 +978,9 @@ func (g *game) Draw(screen *ebiten.Image) {
|
|
|
|
|
g.drawText(screen, 0, soulsY, scale, 1.0, soulsLabel) |
|
|
|
|
} else { |
|
|
|
|
// Draw exit message.
|
|
|
|
|
// TODO flash text
|
|
|
|
|
g.drawText(screen, 0, soulsY, scale, 1.0, "EXIT OPEN") |
|
|
|
|
if time.Since(g.level.exitOpenTime).Milliseconds()%2000 < 1500 { |
|
|
|
|
g.drawText(screen, 0, soulsY, scale, 1.0, "EXIT OPEN") |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -985,7 +994,7 @@ func (g *game) Draw(screen *ebiten.Image) {
@@ -985,7 +994,7 @@ func (g *game) Draw(screen *ebiten.Image) {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if !g.gameWon { |
|
|
|
|
a := g.forceColorScale |
|
|
|
|
a := g.minLevelColorScale |
|
|
|
|
if a == -1 { |
|
|
|
|
a = 1 |
|
|
|
|
} |
|
|
|
@ -1013,9 +1022,9 @@ func (g *game) tilePosition(x, y float64) (float64, float64) {
@@ -1013,9 +1022,9 @@ func (g *game) tilePosition(x, y float64) (float64, float64) {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// renderSprite renders a sprite on the screen.
|
|
|
|
|
func (g *game) renderSprite(x float64, y float64, offsetx float64, offsety float64, angle float64, scale float64, colorScale float64, alpha float64, sprite *ebiten.Image, target *ebiten.Image) int { |
|
|
|
|
if g.forceColorScale != -1 { |
|
|
|
|
colorScale = g.forceColorScale |
|
|
|
|
func (g *game) renderSprite(x float64, y float64, offsetx float64, offsety float64, angle float64, geoScale float64, colorScale float64, alpha float64, sprite *ebiten.Image, target *ebiten.Image) int { |
|
|
|
|
if g.minLevelColorScale != -1 && colorScale < g.minLevelColorScale { |
|
|
|
|
colorScale = g.minLevelColorScale |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if alpha < .01 || colorScale < .01 { |
|
|
|
@ -1033,7 +1042,7 @@ func (g *game) renderSprite(x float64, y float64, offsetx float64, offsety float
@@ -1033,7 +1042,7 @@ func (g *game) renderSprite(x float64, y float64, offsetx float64, offsety float
|
|
|
|
|
|
|
|
|
|
g.op.GeoM.Reset() |
|
|
|
|
|
|
|
|
|
g.op.GeoM.Scale(scale, scale) |
|
|
|
|
g.op.GeoM.Scale(geoScale, geoScale) |
|
|
|
|
// Rotate
|
|
|
|
|
g.op.GeoM.Translate(-16+offsetx, -16+offsety) |
|
|
|
|
g.op.GeoM.Rotate(angle) |
|
|
|
@ -1057,81 +1066,48 @@ func (g *game) renderSprite(x float64, y float64, offsetx float64, offsety float
@@ -1057,81 +1066,48 @@ func (g *game) renderSprite(x float64, y float64, offsetx float64, offsety float
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Calculate color scale to apply shadows.
|
|
|
|
|
func (g *game) colorScale(x, y float64) float64 { |
|
|
|
|
if g.gameWon || g.fullBrightMode { |
|
|
|
|
func (g *game) levelColorScale(x, y float64) float64 { |
|
|
|
|
if g.fullBrightMode { |
|
|
|
|
return 1 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
v := colorScaleValue(x, y, g.player.x, g.player.y) |
|
|
|
|
var v float64 |
|
|
|
|
if g.player.hasTorch { |
|
|
|
|
v = colorScaleValue(x, y, g.player.x, g.player.y) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
tileV := g.level.Tile(int(x), int(y)).colorScale |
|
|
|
|
t := g.level.Tile(int(x), int(y)) |
|
|
|
|
if t == nil { |
|
|
|
|
return 0 |
|
|
|
|
} |
|
|
|
|
tileV := t.colorScale |
|
|
|
|
|
|
|
|
|
s := math.Min(1, v+tileV) |
|
|
|
|
|
|
|
|
|
return s |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// renderLevel draws the current Level on the screen.
|
|
|
|
|
func (g *game) renderLevel(screen *ebiten.Image) int { |
|
|
|
|
func (g *game) drawProjectiles(screen *ebiten.Image) int { |
|
|
|
|
var drawn int |
|
|
|
|
|
|
|
|
|
drawCreeps := func() { |
|
|
|
|
for _, c := range g.level.creeps { |
|
|
|
|
if c.health == 0 && c.creepType != TypeTorch { |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
a := 1.0 |
|
|
|
|
if c.creepType == TypeSoul { |
|
|
|
|
a = 0.35 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
drawn += g.renderSprite(c.x, c.y, 0, 0, c.angle, 1.0, g.colorScale(c.x, c.y), a, c.sprites[c.frame], screen) |
|
|
|
|
if c.frames > 1 && time.Since(c.lastFrame) >= 75*time.Millisecond { |
|
|
|
|
c.frame++ |
|
|
|
|
if c.frame == c.frames { |
|
|
|
|
c.frame = 0 |
|
|
|
|
} |
|
|
|
|
c.lastFrame = time.Now() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var t *Tile |
|
|
|
|
for y := 0; y < g.level.h; y++ { |
|
|
|
|
for x := 0; x < g.level.w; x++ { |
|
|
|
|
t = g.level.tiles[y][x] |
|
|
|
|
if t == nil { |
|
|
|
|
continue // No tile at this position.
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for i := range t.sprites { |
|
|
|
|
drawn += g.renderSprite(float64(x), float64(y), 0, 0, 0, 1.0, g.colorScale(float64(x), float64(y)), 1.0, t.sprites[i], screen) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for _, item := range g.level.items { |
|
|
|
|
if item.health == 0 { |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
drawn += g.renderSprite(item.x, item.y, 0, 0, 0, 1.0, g.colorScale(item.x, item.y), 1.0, item.sprite, screen) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if !g.gameWon { |
|
|
|
|
drawCreeps() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for _, p := range g.projectiles { |
|
|
|
|
colorScale := p.colorScale |
|
|
|
|
if colorScale == 1 { |
|
|
|
|
colorScale = g.colorScale(p.x, p.y) |
|
|
|
|
colorScale = g.levelColorScale(p.x, p.y) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
drawn += g.renderSprite(p.x, p.y, 0, 0, p.angle, 1.0, colorScale, 1.0, imageAtlas[ImageBullet], screen) |
|
|
|
|
alpha := 1.0 |
|
|
|
|
if g.gameWon { |
|
|
|
|
//alpha = g.minLevelColorScale
|
|
|
|
|
} |
|
|
|
|
// TODO if colorscale and gamewon, alpha is colorscale
|
|
|
|
|
|
|
|
|
|
drawn += g.renderSprite(p.x, p.y, 0, 0, p.angle, 1.0, colorScale, alpha, imageAtlas[ImageBullet], screen) |
|
|
|
|
} |
|
|
|
|
return drawn |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (g *game) drawPlayer(screen *ebiten.Image) int { |
|
|
|
|
var drawn int |
|
|
|
|
|
|
|
|
|
repelTime := g.player.garlicUntil.Sub(time.Now()) |
|
|
|
|
if repelTime > 0 && repelTime < 7*time.Second { |
|
|
|
@ -1155,6 +1131,11 @@ func (g *game) renderLevel(screen *ebiten.Image) int {
@@ -1155,6 +1131,11 @@ func (g *game) renderLevel(screen *ebiten.Image) int {
|
|
|
|
|
drawn += g.renderSprite(g.player.x+0.25, g.player.y+0.25, -offset, -offset, 0, scale, 1.0, alpha, imageAtlas[ImageHolyWater], screen) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var playerColorScale = g.levelColorScale(g.player.x, g.player.y) |
|
|
|
|
if g.minPlayerColorScale != -1 { |
|
|
|
|
playerColorScale = g.minPlayerColorScale |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var weaponSprite *ebiten.Image |
|
|
|
|
|
|
|
|
|
playerSprite := playerSS.Frame1 |
|
|
|
@ -1163,7 +1144,7 @@ func (g *game) renderLevel(screen *ebiten.Image) int {
@@ -1163,7 +1144,7 @@ func (g *game) renderLevel(screen *ebiten.Image) int {
|
|
|
|
|
if g.player.weapon != nil { |
|
|
|
|
weaponSprite = g.player.weapon.spriteFlipped |
|
|
|
|
} |
|
|
|
|
if g.player.angle > math.Pi/2 || g.player.angle < -1*math.Pi/2 { |
|
|
|
|
if (g.player.angle > math.Pi/2 || g.player.angle < -1*math.Pi/2) && (g.gameOverTime.IsZero() || time.Since(g.gameOverTime) < 7*time.Second) { |
|
|
|
|
playerSprite = playerSS.Frame2 |
|
|
|
|
playerAngle = playerAngle - math.Pi |
|
|
|
|
mul = -1 |
|
|
|
@ -1171,19 +1152,84 @@ func (g *game) renderLevel(screen *ebiten.Image) int {
@@ -1171,19 +1152,84 @@ func (g *game) renderLevel(screen *ebiten.Image) int {
|
|
|
|
|
weaponSprite = g.player.weapon.sprite |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
drawn += g.renderSprite(g.player.x, g.player.y, 0, 0, playerAngle, 1.0, 1.0, 1.0, playerSprite, screen) |
|
|
|
|
drawn += g.renderSprite(g.player.x, g.player.y, 0, 0, playerAngle, 1.0, playerColorScale, 1.0, playerSprite, screen) |
|
|
|
|
if g.player.weapon != nil { |
|
|
|
|
drawn += g.renderSprite(g.player.x, g.player.y, 11*mul, 9, playerAngle, 1.0, 1.0, 1.0, weaponSprite, screen) |
|
|
|
|
drawn += g.renderSprite(g.player.x, g.player.y, 11*mul, 9, playerAngle, 1.0, playerColorScale, 1.0, weaponSprite, screen) |
|
|
|
|
} |
|
|
|
|
if g.player.hasTorch { |
|
|
|
|
drawn += g.renderSprite(g.player.x, g.player.y, -10*mul, 2, playerAngle, 1.0, 1.0, 1.0, sandstoneSS.TorchMulti, screen) |
|
|
|
|
drawn += g.renderSprite(g.player.x, g.player.y, -10*mul, 2, playerAngle, 1.0, playerColorScale, 1.0, sandstoneSS.TorchMulti, screen) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
flashDuration := 40 * time.Millisecond |
|
|
|
|
if g.player.weapon != nil && time.Since(g.player.weapon.lastFire) < flashDuration { |
|
|
|
|
drawn += g.renderSprite(g.player.x, g.player.y, 39, -1, g.player.angle, 1.0, 1.0, 1.0, imageAtlas[ImageMuzzleFlash], screen) |
|
|
|
|
drawn += g.renderSprite(g.player.x, g.player.y, 39, -1, g.player.angle, 1.0, playerColorScale, 1.0, imageAtlas[ImageMuzzleFlash], screen) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return drawn |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// renderLevel draws the current Level on the screen.
|
|
|
|
|
func (g *game) renderLevel(screen *ebiten.Image) int { |
|
|
|
|
var drawn int |
|
|
|
|
|
|
|
|
|
drawCreeps := func() { |
|
|
|
|
for _, c := range g.level.creeps { |
|
|
|
|
if c.health == 0 && c.creepType != TypeTorch { |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
a := 1.0 |
|
|
|
|
if c.creepType == TypeSoul { |
|
|
|
|
a = 0.35 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
drawn += g.renderSprite(c.x, c.y, 0, 0, c.angle, 1.0, g.levelColorScale(c.x, c.y), a, c.sprites[c.frame], screen) |
|
|
|
|
if c.frames > 1 && time.Since(c.lastFrame) >= 75*time.Millisecond { |
|
|
|
|
c.frame++ |
|
|
|
|
if c.frame == c.frames { |
|
|
|
|
c.frame = 0 |
|
|
|
|
} |
|
|
|
|
c.lastFrame = time.Now() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if g.gameWon { |
|
|
|
|
drawn += g.drawProjectiles(screen) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var t *Tile |
|
|
|
|
for y := 0; y < g.level.h; y++ { |
|
|
|
|
for x := 0; x < g.level.w; x++ { |
|
|
|
|
t = g.level.tiles[y][x] |
|
|
|
|
if t == nil { |
|
|
|
|
continue // No tile at this position.
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for i := range t.sprites { |
|
|
|
|
drawn += g.renderSprite(float64(x), float64(y), 0, 0, 0, 1.0, g.levelColorScale(float64(x), float64(y)), 1.0, t.sprites[i], screen) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for _, item := range g.level.items { |
|
|
|
|
if item.health == 0 { |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
drawn += g.renderSprite(item.x, item.y, 0, 0, 0, 1.0, g.levelColorScale(item.x, item.y), 1.0, item.sprite, screen) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if !g.gameWon { |
|
|
|
|
drawCreeps() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if !g.gameWon { |
|
|
|
|
drawn += g.drawProjectiles(screen) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
drawn += g.drawPlayer(screen) |
|
|
|
|
|
|
|
|
|
if g.gameWon { |
|
|
|
|
drawCreeps() |
|
|
|
|
} |
|
|
|
@ -1334,9 +1380,7 @@ func (g *game) showWinScreen() {
@@ -1334,9 +1380,7 @@ func (g *game) showWinScreen() {
|
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// TODO make it a sunrise instead
|
|
|
|
|
|
|
|
|
|
g.forceColorScale = 0.4 |
|
|
|
|
g.minLevelColorScale = 0.4 |
|
|
|
|
|
|
|
|
|
g.gameWon = true |
|
|
|
|
g.gameOverTime = time.Now() |
|
|
|
@ -1349,8 +1393,6 @@ func (g *game) showWinScreen() {
@@ -1349,8 +1393,6 @@ func (g *game) showWinScreen() {
|
|
|
|
|
|
|
|
|
|
g.level = newWinLevel(g.player) |
|
|
|
|
|
|
|
|
|
// TODO move win screen background upward
|
|
|
|
|
|
|
|
|
|
g.winScreenBackground = ebiten.NewImage(g.w, g.h) |
|
|
|
|
g.winScreenBackground.Fill(colornames.Deepskyblue) |
|
|
|
|
|
|
|
|
@ -1360,6 +1402,207 @@ func (g *game) showWinScreen() {
@@ -1360,6 +1402,207 @@ func (g *game) showWinScreen() {
|
|
|
|
|
|
|
|
|
|
g.winScreenSunY = float64(g.h/2) + float64(sunSize/2) |
|
|
|
|
|
|
|
|
|
go func() { |
|
|
|
|
p := g.player |
|
|
|
|
l := g.level |
|
|
|
|
|
|
|
|
|
var stars []*projectile |
|
|
|
|
|
|
|
|
|
addStar := func() { |
|
|
|
|
star := &projectile{ |
|
|
|
|
x: p.x + (0.5-rand.Float64())*66, |
|
|
|
|
y: p.y + (0.5-rand.Float64())*66, |
|
|
|
|
colorScale: rand.Float64(), |
|
|
|
|
} |
|
|
|
|
g.projectiles = append(g.projectiles, star) |
|
|
|
|
stars = append(stars, star) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
lastPlayerX := p.x |
|
|
|
|
updateStars := func() { |
|
|
|
|
if p.x == lastPlayerX { |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for _, star := range stars { |
|
|
|
|
star.x = p.x - (lastPlayerX - star.x) |
|
|
|
|
} |
|
|
|
|
lastPlayerX = p.x |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Add stars.
|
|
|
|
|
numStars := 666 |
|
|
|
|
for i := 0; i < numStars; i++ { |
|
|
|
|
addStar() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Walk away.
|
|
|
|
|
for i := 0; i < 36; i++ { |
|
|
|
|
p.x += 0.05 |
|
|
|
|
updateStars() |
|
|
|
|
time.Sleep(time.Second / 144) |
|
|
|
|
} |
|
|
|
|
for i := 0; i < 288; i++ { |
|
|
|
|
p.x += 0.05 * (float64(288-i) / 288) |
|
|
|
|
updateStars() |
|
|
|
|
|
|
|
|
|
time.Sleep(time.Second / 144) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Turn around.
|
|
|
|
|
p.angle = math.Pi |
|
|
|
|
time.Sleep(time.Millisecond * 1750) |
|
|
|
|
|
|
|
|
|
// Throw weapon.
|
|
|
|
|
weaponSprite := newCreep(TypeTorch, l, p) |
|
|
|
|
weaponSprite.x, weaponSprite.y = p.x, p.y |
|
|
|
|
weaponSprite.frames = 1 |
|
|
|
|
weaponSprite.frame = 0 |
|
|
|
|
weaponSprite.sprites = []*ebiten.Image{ |
|
|
|
|
imageAtlas[ImageUzi], |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
p.weapon = nil |
|
|
|
|
l.creeps = append(l.creeps, weaponSprite) |
|
|
|
|
|
|
|
|
|
startX := 108 |
|
|
|
|
|
|
|
|
|
doorX := float64(startX) - 0.4 |
|
|
|
|
|
|
|
|
|
go func() { |
|
|
|
|
for i := 0; i < 144*2; i++ { |
|
|
|
|
if weaponSprite.x < doorX { |
|
|
|
|
for i, c := range l.creeps { |
|
|
|
|
if c == weaponSprite { |
|
|
|
|
l.creeps = append(l.creeps[:i], l.creeps[i+1:]...) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
weaponSprite.x -= 0.05 |
|
|
|
|
if i < 100 { |
|
|
|
|
weaponSprite.y -= 0.005 * (float64(144-i) / 144) |
|
|
|
|
} else { |
|
|
|
|
weaponSprite.y += 0.01 * (float64(288-i) / 288) |
|
|
|
|
} |
|
|
|
|
weaponSprite.angle -= .1 |
|
|
|
|
time.Sleep(time.Second / 144) |
|
|
|
|
} |
|
|
|
|
}() |
|
|
|
|
|
|
|
|
|
time.Sleep(time.Second / 2) |
|
|
|
|
|
|
|
|
|
// Throw torch.
|
|
|
|
|
torchSprite := newCreep(TypeTorch, l, p) |
|
|
|
|
torchSprite.x, torchSprite.y = p.x, p.y |
|
|
|
|
torchSprite.frames = 1 |
|
|
|
|
torchSprite.frame = 0 |
|
|
|
|
torchSprite.sprites = []*ebiten.Image{ |
|
|
|
|
sandstoneSS.TorchMulti, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
p.hasTorch = false |
|
|
|
|
l.creeps = append(l.creeps, torchSprite) |
|
|
|
|
l.torches = append(l.torches, torchSprite) |
|
|
|
|
l.bakePartialLightmap(int(torchSprite.x), int(torchSprite.y)) |
|
|
|
|
|
|
|
|
|
go func() { |
|
|
|
|
lastTorchX := torchSprite.x |
|
|
|
|
|
|
|
|
|
for i := 0; i < 144*3; i++ { |
|
|
|
|
if torchSprite.x < doorX { |
|
|
|
|
for i, c := range l.creeps { |
|
|
|
|
if c == torchSprite { |
|
|
|
|
l.creeps = append(l.creeps[:i], l.creeps[i+1:]...) |
|
|
|
|
l.torches = nil |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
torchSprite.x -= 0.05 |
|
|
|
|
if i < 100 { |
|
|
|
|
torchSprite.y -= 0.005 * (float64(144-i) / 144) |
|
|
|
|
} else { |
|
|
|
|
torchSprite.y += 0.01 * (float64(288-i) / 288) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if lastTorchX-torchSprite.x >= 0.1 { |
|
|
|
|
l.bakePartialLightmap(int(torchSprite.x), int(torchSprite.y)) |
|
|
|
|
lastTorchX = torchSprite.x |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
torchSprite.angle -= .1 |
|
|
|
|
time.Sleep(time.Second / 144) |
|
|
|
|
} |
|
|
|
|
}() |
|
|
|
|
|
|
|
|
|
// Walk away.
|
|
|
|
|
time.Sleep(time.Second) |
|
|
|
|
|
|
|
|
|
p.angle = 0 |
|
|
|
|
for i := 0; i < 144; i++ { |
|
|
|
|
p.x += 0.05 * (float64(i) / 144) |
|
|
|
|
// Fade out stars.
|
|
|
|
|
for _, star := range stars { |
|
|
|
|
star.colorScale -= 0.01 |
|
|
|
|
if star.colorScale < 0 { |
|
|
|
|
star.colorScale = 0 |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
updateStars() |
|
|
|
|
time.Sleep(time.Second / 144) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var removedExistingStars bool |
|
|
|
|
|
|
|
|
|
var addedStars bool |
|
|
|
|
|
|
|
|
|
for i := 0; i < 144*20; i++ { |
|
|
|
|
if p.health > 0 { |
|
|
|
|
// Game has restarted.
|
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if i > int(144*3.5) { |
|
|
|
|
if !removedExistingStars { |
|
|
|
|
// Remove existing stars.
|
|
|
|
|
stars = nil |
|
|
|
|
g.projectiles = nil |
|
|
|
|
|
|
|
|
|
removedExistingStars = true |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if i > int(144*12) { |
|
|
|
|
p.angle += 0.0025 * (float64((144*5)-i) / (144 * 5)) |
|
|
|
|
|
|
|
|
|
addStar() |
|
|
|
|
addStar() |
|
|
|
|
addStar() |
|
|
|
|
addStar() |
|
|
|
|
_ = addedStars |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
p.x += 0.05 |
|
|
|
|
updateStars() |
|
|
|
|
|
|
|
|
|
if i > 144*11 { |
|
|
|
|
for _, star := range stars { |
|
|
|
|
pct := float64((144*7)-i) / 144 * 7 |
|
|
|
|
|
|
|
|
|
star.x -= 0.05 * pct / 50 |
|
|
|
|
|
|
|
|
|
// Apply warp effect.
|
|
|
|
|
dx, dy := deltaXY(g.player.x, g.player.y, star.x, star.y) |
|
|
|
|
star.x, star.y = star.x+(dx/100)*pct/1000, star.y+(dy/100)*pct/1000 |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
time.Sleep(time.Second / 144) |
|
|
|
|
} |
|
|
|
|
}() |
|
|
|
|
|
|
|
|
|
go func() { |
|
|
|
|
// Animate sunrise.
|
|
|
|
|
go func() { |
|
|
|
@ -1368,9 +1611,9 @@ func (g *game) showWinScreen() {
@@ -1368,9 +1611,9 @@ func (g *game) showWinScreen() {
|
|
|
|
|
g.winScreenSunY -= 0.035 |
|
|
|
|
|
|
|
|
|
if i > int(144*3.5) { |
|
|
|
|
g.forceColorScale += 0.0005 |
|
|
|
|
if g.forceColorScale > 1 { |
|
|
|
|
g.forceColorScale = 1 |
|
|
|
|
g.minLevelColorScale += 0.0005 |
|
|
|
|
if g.minLevelColorScale > 1 { |
|
|
|
|
g.minLevelColorScale = 1 |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -1385,22 +1628,36 @@ func (g *game) showWinScreen() {
@@ -1385,22 +1628,36 @@ func (g *game) showWinScreen() {
|
|
|
|
|
time.Sleep(time.Second / 144) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
time.Sleep(5 * time.Second) |
|
|
|
|
time.Sleep(4 * time.Second) |
|
|
|
|
|
|
|
|
|
// Fade out win screen.
|
|
|
|
|
|
|
|
|
|
for i := 0; i < 144; i++ { |
|
|
|
|
g.winScreenColorScale -= 0.01 |
|
|
|
|
g.forceColorScale -= 0.01 |
|
|
|
|
for i := 0; i < 144*2; i++ { |
|
|
|
|
g.winScreenColorScale -= 0.005 |
|
|
|
|
g.minLevelColorScale -= 0.005 |
|
|
|
|
if g.minLevelColorScale > 0.6 { |
|
|
|
|
g.minPlayerColorScale = g.minLevelColorScale |
|
|
|
|
} |
|
|
|
|
time.Sleep(time.Second / 144) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
g.Lock() |
|
|
|
|
for y := 0; y < g.level.h; y++ { |
|
|
|
|
for x := 0; x < g.level.w; x++ { |
|
|
|
|
g.level.tiles[y][x].sprites = nil |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
g.Unlock() |
|
|
|
|
|
|
|
|
|
time.Sleep(6 * time.Second) |
|
|
|
|
|
|
|
|
|
// Fade in game over screen.
|
|
|
|
|
|
|
|
|
|
g.gameWon = false |
|
|
|
|
|
|
|
|
|
defer func() { |
|
|
|
|
g.forceColorScale = -1 |
|
|
|
|
g.minLevelColorScale = -1 |
|
|
|
|
g.minPlayerColorScale = -1 |
|
|
|
|
g.updateCursor() |
|
|
|
|
}() |
|
|
|
|
|
|
|
|
@ -1408,7 +1665,9 @@ func (g *game) showWinScreen() {
@@ -1408,7 +1665,9 @@ func (g *game) showWinScreen() {
|
|
|
|
|
if g.player.health > 0 { |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
g.forceColorScale = i |
|
|
|
|
if i <= 1 { |
|
|
|
|
g.minLevelColorScale = i |
|
|
|
|
} |
|
|
|
|
time.Sleep(time.Second / 144) |
|
|
|
|
} |
|
|
|
|
}() |
|
|
|
|