313 lines
9.8 KiB
Go
313 lines
9.8 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"image/color"
|
|
"math/rand"
|
|
"strconv"
|
|
"time"
|
|
|
|
"opslag.de/schobers/geom"
|
|
"opslag.de/schobers/tins2021"
|
|
"opslag.de/schobers/zntg"
|
|
"opslag.de/schobers/zntg/ui"
|
|
)
|
|
|
|
type levelController struct {
|
|
ui.ControlBase
|
|
|
|
app *appContext
|
|
|
|
Level *tins2021.Level
|
|
|
|
Cubes cubeTexture
|
|
Animations map[string]*tins2021.Animations
|
|
|
|
IdleMonsters *tins2021.Animations
|
|
MovingMonsters *tins2021.Animations
|
|
|
|
SmallFont *tins2021.BitmapFont
|
|
}
|
|
|
|
func newLevelControl(app *appContext, ctx ui.Context, level *tins2021.Level) *levelController {
|
|
control := &levelController{app: app}
|
|
textures := ctx.Textures()
|
|
control.Cubes = newCubeTexture(textures, tins2021.Orange)
|
|
|
|
small, err := tins2021.NewBitmapFont(ctx.Renderer(), ctx.Fonts().Font("small"), tins2021.NumericCharacters...)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
control.SmallFont = small
|
|
|
|
control.Play(level)
|
|
|
|
return control
|
|
}
|
|
|
|
func IsModifierPressed(modifiers ui.KeyModifier, pressed ui.KeyModifier) bool {
|
|
return modifiers&pressed == pressed
|
|
}
|
|
|
|
func (r *levelController) Handle(ctx ui.Context, e ui.Event) bool {
|
|
switch e := e.(type) {
|
|
case *ui.KeyDownEvent:
|
|
switch e.Key {
|
|
case ui.KeyEscape:
|
|
r.app.ShowMainMenu(ctx)
|
|
}
|
|
}
|
|
|
|
if r.Level.GameOver {
|
|
return false
|
|
}
|
|
|
|
if r.Level.StarsCollected == r.Level.Stars {
|
|
switch e := e.(type) {
|
|
case *ui.KeyDownEvent:
|
|
switch e.Key {
|
|
case ui.KeyEnter:
|
|
r.app.PlayNext(ctx, r)
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
switch e := e.(type) {
|
|
case *ui.KeyDownEvent:
|
|
switch e.Key {
|
|
case ui.KeyW:
|
|
r.Level.MovePlayer(tins2021.DirectionUpLeft)
|
|
case ui.KeyD:
|
|
r.Level.MovePlayer(tins2021.DirectionUpRight)
|
|
case ui.KeyS:
|
|
r.Level.MovePlayer(tins2021.DirectionDownRight)
|
|
case ui.KeyA:
|
|
r.Level.MovePlayer(tins2021.DirectionDownLeft)
|
|
}
|
|
}
|
|
|
|
for _, animations := range r.Animations {
|
|
animations.Update()
|
|
}
|
|
r.IdleMonsters.Update()
|
|
r.MovingMonsters.Update()
|
|
var jumped []geom.Point
|
|
for pos, animation := range r.MovingMonsters.Values {
|
|
if animation.Frame < 15 {
|
|
continue
|
|
}
|
|
target := r.Level.MonsterTargets[pos]
|
|
if target == r.Level.Player { // player is hit
|
|
r.Level.DestroyMonster(pos)
|
|
jumped = append(jumped, pos)
|
|
r.Level.DecrementLive()
|
|
continue
|
|
}
|
|
if animation.Frame < 20 {
|
|
continue
|
|
}
|
|
r.Level.MoveMonster(target, pos)
|
|
jumped = append(jumped, pos)
|
|
|
|
r.IdleMonsters.Frame(target)
|
|
}
|
|
for _, pos := range jumped {
|
|
delete(r.MovingMonsters.Values, pos)
|
|
}
|
|
|
|
var jumping []geom.Point
|
|
for pos, animation := range r.IdleMonsters.Values {
|
|
for animation.Frame > 0 {
|
|
if rand.Intn(10) != 0 {
|
|
monster, ok := r.Level.Monsters[pos]
|
|
if ok && monster != nil {
|
|
target, ok := monster.FindTarget(r.Level, pos)
|
|
if ok {
|
|
r.Level.MonsterTargets[pos] = target
|
|
r.MovingMonsters.Frame(pos)
|
|
jumping = append(jumping, pos)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
animation.Frame--
|
|
}
|
|
}
|
|
for _, pos := range jumping {
|
|
delete(r.IdleMonsters.Values, pos)
|
|
}
|
|
|
|
ctx.Animate()
|
|
return false
|
|
}
|
|
|
|
func (r *levelController) Play(level *tins2021.Level) {
|
|
r.Level = level
|
|
|
|
r.Animations = map[string]*tins2021.Animations{
|
|
"star": tins2021.NewAnimations(50*time.Millisecond, defaultAnimationFrames, true, true),
|
|
"heart": tins2021.NewAnimations(80*time.Millisecond, defaultAnimationFrames, true, true),
|
|
}
|
|
r.IdleMonsters = tins2021.NewAnimations(500*time.Millisecond, 100, false, false)
|
|
r.MovingMonsters = tins2021.NewAnimations(50*time.Millisecond, 20, false, false)
|
|
for monster := range level.Monsters {
|
|
r.IdleMonsters.Frame(monster)
|
|
}
|
|
for _, monster := range r.app.MonsterTextureNames {
|
|
r.Animations[monster] = tins2021.NewAnimations(80*time.Millisecond, defaultAnimationFrames, true, true)
|
|
}
|
|
}
|
|
|
|
const defaultAnimationFrames = 20
|
|
|
|
func (r levelController) Render(ctx ui.Context) {
|
|
const twelfth = (1. / 6) * geom.Pi
|
|
|
|
centerTopSquare := geom.PtF32(.5, .5*geom.Sin32(twelfth))
|
|
delta := geom.PtF32(geom.Cos32(twelfth), .5+centerTopSquare.Y)
|
|
|
|
view := r.Bounds().Size()
|
|
levelView := geom.PtF32(float32(r.Level.Bounds.Dx()+2)*delta.X, float32(r.Level.Bounds.Dy()+2)*delta.Y)
|
|
textureWidth := geom.Min32(
|
|
geom.Floor32(tins2021.TextureSize*view.X*.75/(levelView.X*tins2021.TextureSize)),
|
|
geom.Floor32(tins2021.TextureSize*view.Y/(levelView.Y*tins2021.TextureSize)))
|
|
scale := textureWidth / tins2021.TextureSize
|
|
offsetY := .5 * (view.Y - (levelView.Y * textureWidth))
|
|
|
|
delta = delta.Mul(textureWidth)
|
|
centerTopSquare = centerTopSquare.Mul(textureWidth)
|
|
scoreView := geom.RectF32(levelView.X*textureWidth, offsetY+delta.Y, view.X, view.Y-delta.Y-offsetY)
|
|
|
|
delta.X = geom.Round32(delta.X)
|
|
delta.Y = geom.Round32(delta.Y)
|
|
toScreen := func(p geom.Point) geom.PointF32 {
|
|
if p.Y%2 == 0 {
|
|
return p.ToF32().Mul2D(delta.XY()).Add2D(.5*delta.X, offsetY)
|
|
}
|
|
return p.ToF32().Mul2D(delta.XY()).Add2D(0, offsetY)
|
|
}
|
|
|
|
renderer := ctx.Renderer()
|
|
textures := ctx.Textures()
|
|
cubes := r.Cubes.Scaled(textures, scale)
|
|
|
|
cubeWidth := float32(cubes.Normal.Width())
|
|
cubeHeight := float32(cubes.Normal.Height())
|
|
|
|
player := ctx.Textures().ScaledByName("dwarf", scale*.6)
|
|
star := tins2021.NewAnimatedTexture(ctx.Textures().ScaledByName("star", scale*.4), defaultAnimationFrames)
|
|
heart := tins2021.NewAnimatedTexture(ctx.Textures().ScaledByName("heart", scale*.4), defaultAnimationFrames)
|
|
monsterTextures := map[tins2021.MonsterType]tins2021.AnimatedTexture{}
|
|
for typ, name := range r.app.MonsterTextureNames {
|
|
monsterTextures[typ] = tins2021.NewAnimatedTexture(ctx.Textures().ScaledByName(name, scale*.4), defaultAnimationFrames)
|
|
}
|
|
propOffset := geom.PtF32(-.5*float32(star.Texture.Height()), -.8*float32(star.Texture.Height()))
|
|
|
|
distances := r.Level.Tiles.Distances(r.Level.Player)
|
|
|
|
positionOfTile := func(position geom.Point, tile *tins2021.Tile) (topLeft, centerOfPlatform geom.PointF32) {
|
|
topLeft = toScreen(position)
|
|
if tile.Inversed {
|
|
return topLeft, topLeft.Add2D(.5*float32(cubeWidth), .6*float32(cubeHeight))
|
|
}
|
|
return topLeft, topLeft.Add2D(.5*float32(cubeWidth), .2*float32(cubeHeight))
|
|
}
|
|
|
|
for y := r.Level.Bounds.Min.Y; y < r.Level.Bounds.Max.Y; y++ {
|
|
for x := r.Level.Bounds.Min.X; x < r.Level.Bounds.Max.X; x++ {
|
|
pos := geom.Pt(x, y)
|
|
tile := r.Level.Tiles[pos]
|
|
if tile == nil {
|
|
continue
|
|
}
|
|
screenPos, platformPos := positionOfTile(pos, tile)
|
|
tileTexture := cubes.Normal.Texture
|
|
if tile.Inversed {
|
|
tileTexture = cubes.Inversed.Texture
|
|
}
|
|
renderer.DrawTexturePoint(tileTexture, screenPos)
|
|
if r.app.Debug {
|
|
r.SmallFont.TextAlign(renderer, platformPos, color.Black, strconv.Itoa(distances[pos]), ui.AlignCenter)
|
|
}
|
|
|
|
if tile.Star {
|
|
star.Draw(renderer, platformPos.Add(propOffset), r.Animations["star"].Frame(pos))
|
|
}
|
|
if tile.Heart {
|
|
heart.Draw(renderer, platformPos.Add(propOffset), r.Animations["heart"].Frame(pos))
|
|
}
|
|
}
|
|
}
|
|
|
|
playerPosition := toScreen(r.Level.Player).Sub(geom.Pt(player.Width()/2, player.Height()).ToF32())
|
|
if r.Level.Tiles[r.Level.Player].Inversed {
|
|
centerBottomSquare := geom.PtF32(centerTopSquare.X, textureWidth-centerTopSquare.Y)
|
|
renderer.DrawTexturePoint(player, playerPosition.Add(centerBottomSquare))
|
|
} else {
|
|
renderer.DrawTexturePoint(player, playerPosition.Add(centerTopSquare))
|
|
}
|
|
|
|
for pos, monsterType := range r.Level.Monsters {
|
|
tile := r.Level.Tiles[pos]
|
|
if tile == nil {
|
|
continue
|
|
}
|
|
_, platformPos := positionOfTile(pos, tile)
|
|
name := r.app.MonsterTextureNames[monsterType.Type()]
|
|
monsterTextures[monsterType.Type()].Draw(renderer, platformPos.Add(propOffset), r.Animations[name].Frame(pos))
|
|
}
|
|
|
|
textColor := ctx.Style().Palette.Text
|
|
scoreFont := ctx.Fonts().Font("score")
|
|
fontOffsetY := .5 * (float32(star.Texture.Height()) - scoreFont.Height())
|
|
|
|
// stars & hearts
|
|
scoreTopLeft := scoreView.Min
|
|
star.Draw(renderer, scoreTopLeft, 0)
|
|
renderer.Text(scoreFont, scoreTopLeft.Add2D(textureWidth, fontOffsetY), textColor, fmt.Sprintf("x %d / %d", r.Level.StarsCollected, r.Level.Stars))
|
|
|
|
scoreTopLeft.Y += textureWidth
|
|
heart.Draw(renderer, scoreTopLeft, 0)
|
|
renderer.Text(scoreFont, scoreTopLeft.Add2D(textureWidth, fontOffsetY), textColor, fmt.Sprintf("x %d", r.Level.Lives))
|
|
|
|
// difficulty & score
|
|
scoreTopLeft = geom.PtF32(scoreView.Min.X, scoreView.Max.Y-scoreFont.Height())
|
|
renderer.Text(scoreFont, scoreTopLeft, textColor, strconv.Itoa(r.Level.Difficulty))
|
|
scoreTopLeft.Y -= scoreFont.Height()
|
|
renderer.Text(scoreFont, scoreTopLeft, textColor, "Difficulty:")
|
|
|
|
scoreTopLeft.Y -= 2 * scoreFont.Height()
|
|
renderer.Text(scoreFont, scoreTopLeft, textColor, strconv.Itoa(r.Level.Score))
|
|
scoreTopLeft.Y -= scoreFont.Height()
|
|
renderer.Text(scoreFont, scoreTopLeft, textColor, "Score:")
|
|
|
|
bounds := r.Bounds()
|
|
centerX := .5 * bounds.Dx()
|
|
titleFont := ctx.Fonts().Font("title")
|
|
|
|
if r.Level.GameOver {
|
|
renderer.FillRectangle(bounds, zntg.MustHexColor(`#0000007F`))
|
|
offsetY := .5*bounds.Dy() - titleFont.Height()
|
|
renderer.TextAlign(titleFont, geom.PtF32(centerX, offsetY), textColor, "GAME OVER", ui.AlignCenter)
|
|
|
|
offsetY += titleFont.Height() + scoreFont.Height()
|
|
renderer.TextAlign(scoreFont, geom.PtF32(centerX, offsetY), textColor, fmt.Sprintf("Final score: %d", r.Level.Score), ui.AlignCenter)
|
|
|
|
offsetY += 2 * scoreFont.Height()
|
|
renderer.TextAlign(scoreFont, geom.PtF32(centerX, offsetY), textColor, "Press [escape] to quit.", ui.AlignCenter)
|
|
} else if r.Level.StarsCollected == r.Level.Stars {
|
|
renderer.FillRectangle(bounds, zntg.MustHexColor(`#0000007F`))
|
|
offsetY := .5*bounds.Dy() - titleFont.Height()
|
|
renderer.TextAlign(titleFont, geom.PtF32(.5*bounds.Dx(), offsetY), textColor, "COMPLETED", ui.AlignCenter)
|
|
|
|
offsetY += titleFont.Height() + scoreFont.Height()
|
|
renderer.TextAlign(scoreFont, geom.PtF32(centerX, offsetY), textColor, fmt.Sprintf("Score: %d", r.Level.Score), ui.AlignCenter)
|
|
|
|
offsetY += 2 * scoreFont.Height()
|
|
renderer.TextAlign(scoreFont, geom.PtF32(centerX, offsetY), textColor, "Press [enter] to continue.", ui.AlignCenter)
|
|
offsetY += scoreFont.Height()
|
|
renderer.TextAlign(scoreFont, geom.PtF32(centerX, offsetY), textColor, "Press [escape] to quit.", ui.AlignCenter)
|
|
}
|
|
}
|