tins2021/cmd/tins2021/levelcontroller.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)
}
}