Finished project. Enemies, PvP, Music, menug

This commit is contained in:
Nefrace 2023-01-08 12:50:21 +03:00
parent 461d177cfa
commit c3de6b0a9a
10 changed files with 660 additions and 88 deletions

8
actor.go Normal file
View File

@ -0,0 +1,8 @@
package main
type Actor interface {
Update() bool
Draw()
TakeHit(Actor)
GetPosition() Vector
}

51
bullet.go Normal file
View File

@ -0,0 +1,51 @@
package main
import "cart/w4"
type Bullet struct {
Position Vector
SpeedX int8
SpeedY int8
Owner Actor
Dead bool
}
func (b *Bullet) GetPosition() Vector {
return b.Position
}
func (b *Bullet) Update() bool {
if b.Dead {
return true
}
b.Position.Move(float64(b.SpeedX), float64(b.SpeedY))
if b.Position.X < 0 || b.Position.X > 320 || b.Position.Y < -160 || b.Position.Y > 320 {
return true
}
for _, actor := range actors {
if actor == Actor(b.Owner) || actor == b {
continue
}
if _, ok := actor.(*Bullet); ok {
continue
}
diff := b.Position.Sub(actor.GetPosition())
if diff.LenSquared() < 60 {
actor.TakeHit(b)
// b.TakeHit(b)
b.Dead = true
return true
}
}
return false
}
func (b *Bullet) Draw() {
*w4.DRAW_COLORS = 0x41
w4.Oval(int(b.Position.X)-3-camX, int(b.Position.Y)-3-camY, 6, 6)
}
func (b *Bullet) TakeHit(from Actor) {
w4.Tone(800, 10<<8, 20, w4.TONE_MODE2)
b.Dead = true
}

100
enemy.go Normal file
View File

@ -0,0 +1,100 @@
package main
import (
"cart/w4"
"math"
)
const enemyWidth = 8
const enemyHeight = 8
const enemyFlags = 1 // BLIT_2BPP
var enemy = [16]byte{0x80, 0x80, 0xaa, 0x80, 0xa6, 0xa4, 0xa9, 0x9a, 0xaa, 0xaa, 0xaa, 0x9a, 0xaa, 0xa8, 0x20, 0x20}
type Enemy struct {
Position Vector
Speed Vector
Dead bool
ShootTimer uint8
}
func (e *Enemy) GetPosition() Vector {
return e.Position
}
func (e *Enemy) Update() bool {
if e.Position.Y > 320 {
return true
}
if e.Dead {
e.Speed.Y = math.Min(e.Speed.Y+0.2, 4)
}
e.Position.MoveVec(e.Speed)
if e.Position.X < 0 && e.Speed.X < 0 {
e.Position.X = 0
e.Speed.X *= -1
}
if e.Position.X > 320 && e.Speed.X > 0 {
e.Position.X = 320
e.Speed.X *= -1
}
if e.ShootTimer > 0 {
e.ShootTimer--
} else if e.Position.X > 0 && e.Position.X < 320 {
e.ShootTimer = 180
distance := math.MaxFloat64
direction := Vector{}
var player *Player
for _, p := range players {
if p.Health == 0 {
continue
}
diff := p.Position.Sub(e.Position)
dis := diff.LenSquared()
if distance > dis {
distance = dis
player = p
direction = diff.Normalized()
}
}
if player != nil {
sx := int8(direction.X*5) / 3
sy := int8(direction.Y*5) / 3
b := &Bullet{
Position: e.Position,
Owner: e,
SpeedX: sx,
SpeedY: sy,
}
w4.Tone(400|300<<16, 3, 5, w4.TONE_PULSE2)
actors = append(actors, b)
}
}
return false
}
func (e *Enemy) Draw() {
*w4.DRAW_COLORS = 0x44
var f uint = w4.BLIT_2BPP
if e.Speed.X < 0 {
f |= w4.BLIT_FLIP_X
}
*w4.DRAW_COLORS = 0x430
w4.Blit(&enemy[0], int(e.Position.X)-camX-4, int(e.Position.Y)-camY-4, 8, 8, f)
}
func (e *Enemy) TakeHit(from Actor) {
if e.Dead {
return
}
if bullet, ok := from.(*Bullet); ok {
if _, ok := bullet.Owner.(*Player); !ok {
return
}
}
enemies--
kills++
e.Dead = true
e.Speed.Y = -3
w4.Tone(300|200<<16, 5, 20, w4.TONE_PULSE2)
}

163
gamestate.go Normal file
View File

@ -0,0 +1,163 @@
package main
import (
"cart/w4"
"math/rand"
"strconv"
"unsafe"
)
var gravity = 0.2
var points []*Point = []*Point{}
var sticks []*Stick = []*Stick{}
var actors []Actor = []Actor{}
var enemies uint = 0
var players []*Player
var playersAlive = 0
var kills int = 0
var lightIndex uint64 = 0
var camX int = 0
var camY int = 0
var deathTimer = 180
var music *Music
func gameStart() {
kills = 0
deathTimer = 180
rand.Seed(int64(frame))
music = &Music{
KickProb: [8]uint8{100, 10, 25, 15, 50, 10, 20, 30},
SnareProb: [8]uint8{0, 0, 70, 20, 20, 10, 70, 30},
LeadProb: [8]uint8{90, 70, 60, 60, 90, 60, 70, 60},
MainNoteIndex: 1,
}
if musicEnabled {
music.Start()
}
points = []*Point{}
sticks = []*Stick{}
actors = []Actor{}
players = []*Player{}
for i := 0.0; i < 8; i++ {
var y1, y2 float64
if int(i)%2 == 0 {
y1 = rand.Float64()*40 - 20
y2 = rand.Float64() * 40
} else {
y1 = rand.Float64() * 40
y2 = rand.Float64()*40 - 20
}
p, s := CreateRope(
Vector{0, y1 + i*30},
Vector{320, y2 + i*30},
14,
)
points = append(points, p...)
sticks = append(sticks, s...)
}
playersAlive = playersCount
for i, ready := range readyPlayers {
if !ready {
continue
}
player := &Player{
Index: uint8(i),
Health: 20,
ShootTimer: 10,
Position: Vector{80, 80},
Speed: Vector{},
Gamepad: gamepads[i],
StickGrabbed: sticks[rand.Intn(len(sticks)-1)],
}
actors = append(actors, player)
players = append(players, player)
}
}
func gameUpdate() {
if (!pvp && playersAlive == 0) || (pvp && playersAlive <= 1) {
if deathTimer--; deathTimer == 0 {
if !pvp && kills > maxKills {
w4.DiskW(unsafe.Pointer(&kills), 4)
}
menuStart()
stateUpdate = menuUpdate
}
}
music.Update()
frame += 1
Simulate(points, sticks)
if frame%120 == 0 && enemies < 10 && !pvp {
spX := rand.Float64()*2 - 1
posY := rand.Float64()*200 + 50
posX := float64(-160)
if spX < 0 {
posX = 480
}
e := &Enemy{
Position: Vector{posX, posY},
Speed: Vector{spX, 0},
ShootTimer: uint8(rand.Uint32() % 120),
}
enemies++
actors = append(actors, e)
}
actorsToRemove := []int{}
for i, actor := range actors {
if actor.Update() {
actorsToRemove = append(actorsToRemove, i)
}
}
for i := len(actorsToRemove) - 1; i > 0; i-- {
actor := actorsToRemove[i]
actors = append(actors[:actor], actors[actor+1:]...)
}
target := players[0]
isDead := false
if *w4.NETPLAY&0b100 != 0 {
playerId := *w4.NETPLAY & 0b11
target = players[playerId]
if target.Health == 0 {
isDead = true
if plr, ok := target.KilledBy.(*Player); ok {
target = plr
}
}
}
camX, camY = int(target.Position.X)-80, int(target.Position.Y)-80
for _, s := range sticks {
s.Draw()
}
for _, p := range points {
p.Draw()
}
for _, actor := range actors {
actor.Draw()
}
*w4.DRAW_COLORS = 0x23
w4.Rect(-160-camX, -100-camY, 160, 500)
w4.Rect(320-camX, -100-camY, 160, 500)
*w4.DRAW_COLORS = 0x4
if !pvp {
w4.Text(strconv.Itoa(kills)+" kills", 1, 1)
}
// w4.Text(strconv.Itoa(playersAlive), 0, 0)
if !isDead {
if !pvp || (pvp && playersAlive > 1) {
w4.Text("health", 57, 151)
hbar := float32(target.Health) / 20 * 80
w4.Rect(int(80-hbar), 150, uint(hbar*2), 10)
*w4.DRAW_COLORS = 0x1
w4.Text("health", 57, 151)
} else {
w4.Text("winner", 57, 151)
}
} else {
w4.Text(" dead ", 57, 151)
}
}

BIN
logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 895 B

63
main.go
View File

@ -1,74 +1,23 @@
package main
import (
"cart/w4"
"math/rand"
)
import "cart/w4"
var gravity = 0.2
var points []*Point = []*Point{}
var sticks []*Stick = []*Stick{}
var player *Player
var frame uint64 = 0
var lightIndex uint64 = 0
var camX int = 0
var camY int = 0
var stateUpdate func()
//go:export start
func start() {
rand.Seed(654348654)
points = []*Point{}
sticks = []*Stick{}
frame = 0
w4.PALETTE[0] = 0xfcdeea
w4.PALETTE[1] = 0x012824
w4.PALETTE[2] = 0x265935
w4.PALETTE[3] = 0xff4d6d
for i := 0.0; i < 8; i++ {
var y1, y2 float64
if int(i)%2 == 0 {
y1 = rand.Float64()*40 - 20
y2 = rand.Float64() * 40
} else {
y1 = rand.Float64() * 40
y2 = rand.Float64()*40 - 20
}
p, s := CreateRope(
Vector{0, y1 + i*30},
Vector{320, y2 + i*30},
14,
)
points = append(points, p...)
sticks = append(sticks, s...)
}
player = &Player{
Position: Vector{80, 80},
Speed: Vector{},
Gamepad: w4.GAMEPAD1,
StickGrabbed: sticks[rand.Intn(len(sticks)-1)],
}
menuStart()
}
//go:export update
func update() {
frame += 1
*w4.DRAW_COLORS = 2
// w4.Text("Hello from Go!", 10, 10)
Simulate(points, sticks)
player.Update()
camX, camY = int(player.Position.X)-80, int(player.Position.Y)-80
for _, s := range sticks {
s.Draw()
}
for _, p := range points {
p.Draw()
}
player.Draw()
*w4.DRAW_COLORS = 0x23
w4.Rect(-160-camX, -100-camY, 160, 500)
w4.Rect(320-camX, -100-camY, 160, 500)
frame++
stateUpdate()
}

90
menu.go Normal file
View File

@ -0,0 +1,90 @@
package main
import (
"cart/w4"
"strconv"
"unsafe"
)
var readyPlayers = [4]bool{false, false, false, false}
var pvp = false
var playersCount = 0
var maxKills int = 0
var gamepads = []*uint8{
w4.GAMEPAD1,
w4.GAMEPAD2,
w4.GAMEPAD3,
w4.GAMEPAD4,
}
var options = []string{"Survival (1P)", "PvP (Netplay only)", "Music: ON"}
var selected = 0
var musicEnabled = true
var lastGamepad uint8
func menuStart() {
w4.DiskR(unsafe.Pointer(&maxKills), 4)
playersCount = 0
readyPlayers = [4]bool{false, false, false, false}
stateUpdate = menuUpdate
selected = 0
}
func menuUpdate() {
frame++
gpPressed := *w4.GAMEPAD1 & (*w4.GAMEPAD1 ^ lastGamepad)
lastGamepad = *w4.GAMEPAD1
if gpPressed&w4.BUTTON_DOWN != 0 {
selected = (selected + 1) % len(options)
}
if gpPressed&w4.BUTTON_UP != 0 {
selected = (selected - 1) % len(options)
}
if gpPressed&w4.BUTTON_1 != 0 {
switch selected {
case 0:
readyPlayers = [4]bool{true, false, false, false}
playersCount = 1
pvp = false
gameStart()
stateUpdate = gameUpdate
case 1:
if playersCount > 1 {
pvp = true
gameStart()
stateUpdate = gameUpdate
}
case 2:
musicEnabled = !musicEnabled
if musicEnabled {
options[2] = "Music: ON"
} else {
options[2] = "Music: OFF"
}
}
}
for i, gp := range gamepads {
if *gp&w4.BUTTON_2 != 0 {
if !readyPlayers[i] {
readyPlayers[i] = true
playersCount++
}
}
}
w4.Text(strconv.Itoa(maxKills)+" kills", 2, 40)
w4.Text("CHRISTMAS\nLIGHTS\nMASSACRE", 70, 2)
w4.Text("Press Z to get ready", 0, 150)
for i, opt := range options {
*w4.DRAW_COLORS = 0x3
if i == selected {
*w4.DRAW_COLORS = 0x4
}
w4.Text(opt, 2, 60+i*9)
*w4.DRAW_COLORS = 0x2
}
for i, v := range readyPlayers {
if v {
w4.Text("Player "+strconv.Itoa(i+1)+" is ready", 8, 100+i*8)
}
}
}

142
music.go Normal file
View File

@ -0,0 +1,142 @@
package main
import (
"cart/w4"
"math/rand"
)
const (
C int = iota
Cs
D
Ds
E
F
Fs
G
Gs
A
As
B
)
var frequencies = []uint{262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494}
type Music struct {
KickProb [8]uint8
KickLine [8]bool
SnareProb [8]uint8
SnareLine [8]bool
LeadProb [8]uint8
LeadLine [8]uint
MainNoteIndex int
Frame uint64
CurrentPart uint8
CurrentLine uint8
Active bool
MutedFrames uint
}
func (m *Music) Start() {
m.GenLines()
m.Frame = 0
m.CurrentLine = 0
m.CurrentPart = 0
m.Active = true
}
func (m *Music) Pause() {
m.Active = false
}
func (m *Music) Resume() {
m.Active = true
m.MutedFrames = 0
}
func (m *Music) MuteFor(frames uint) {
m.MutedFrames = frames
}
var pentaScale = []int{0, 3, 5, 7, 10, 12}
func (m *Music) PentaNote(n int) uint {
pentaIndex := n % 6
octave := 1 + n/6
// w4.Trace("Octave: " + strconv.Itoa(octave))
noteIndex := (m.MainNoteIndex + pentaScale[pentaIndex]) % 12
return frequencies[noteIndex] * uint(octave)
}
func (m *Music) Update() {
if !m.Active {
return
}
if m.MutedFrames > 0 {
m.MutedFrames--
}
if m.Frame%15 == 0 {
if m.MutedFrames == 0 {
if m.KickLine[m.CurrentLine] {
w4.Tone(200|1<<16, 5, 60, w4.TONE_TRIANGLE)
}
if m.SnareLine[m.CurrentLine] {
w4.Tone(200|50<<16, 10<<8, 10, w4.TONE_NOISE)
}
if m.LeadLine[m.CurrentLine] != 0 {
w4.Tone(m.LeadLine[m.CurrentLine], 10|15<<8, 5, w4.TONE_MODE2|w4.TONE_PULSE2)
}
}
m.CurrentLine++
if m.CurrentLine == 8 {
m.CurrentLine = 0
m.CurrentPart++
if m.CurrentPart == 4 {
m.CurrentPart = 0
m.GenLines()
}
}
}
m.Frame++
}
func (m *Music) GenLines() {
m.KickLine = m.GenDrumLine(m.KickProb)
m.SnareLine = m.GenDrumLine(m.SnareProb)
m.LeadLine = m.GenLeadLine(m.LeadProb)
m.MainNoteIndex = rand.Intn(5)
}
func (m *Music) GenDrumLine(prob [8]uint8) [8]bool {
line := [8]bool{}
for i, p := range prob {
line[i] = rand.Intn(101) < int(p)
}
return line
}
func (m *Music) GenLeadLine(prob [8]uint8) [8]uint {
line := [8]uint{}
pattern := [8]int{}
offset := rand.Intn(6)
switch rand.Intn(4) {
case 0:
pattern = [8]int{0, 1, 2, 3, 0, 1, 2, 3}
case 1:
pattern = [8]int{3, 2, 1, 0, 3, 2, 1, 0}
case 2:
pattern = [8]int{7, 5, 6, 4, 5, 3, 4, 2}
case 3:
pattern = [8]int{0, 2, 1, 3, 2, 4, 3, 5}
case 4:
pattern = [8]int{0, 2, 4, 1, 3, 5, 2, 4}
case 5:
pattern = [8]int{4, 2, 5, 3, 1, 4, 2, 0}
}
for i, p := range prob {
if rand.Intn(101) < int(p) {
line[i] = m.PentaNote(pattern[i]+offset) / 2
continue
}
line[i] = 0
}
return line
}

114
player.go
View File

@ -7,39 +7,64 @@ import (
)
type Player struct {
Index uint8
Health uint8
KilledBy Actor
Position Vector
Speed Vector
//PointGrabbed *Point
StickGrabbed *Stick
StickOffset float64
GrabTimeout uint
GrabTimer uint8
ShootTimer uint8
ShootDirX int8
ShootDirY int8
Gamepad *uint8
GamepadLast uint8
}
func (p *Player) Update() {
func (p *Player) GetPosition() Vector {
return p.Position
}
func (p *Player) Update() bool {
var dirX int8 = int8(*p.Gamepad&w4.BUTTON_RIGHT>>5) - int8(*p.Gamepad&w4.BUTTON_LEFT>>4)
var dirY int8 = int8(*p.Gamepad&w4.BUTTON_DOWN>>7) - int8(*p.Gamepad&w4.BUTTON_UP>>6)
if !(dirX == 0 && dirY == 0) && p.Health > 0 {
p.ShootDirX = dirX
p.ShootDirY = dirY
}
lastGamepad := *p.Gamepad & (*p.Gamepad ^ p.GamepadLast)
p.GamepadLast = *p.Gamepad
if p.GrabTimeout > 0 {
p.GrabTimeout--
if p.GrabTimer > 0 {
p.GrabTimer--
}
if p.ShootTimer > 0 {
p.ShootTimer--
} else {
if *p.Gamepad&w4.BUTTON_1 != 0 && !(p.ShootDirX == 0 && p.ShootDirY == 0) {
bullet := &Bullet{
Owner: p,
Position: p.Position,
SpeedX: p.ShootDirX * 3,
SpeedY: p.ShootDirY * 3,
}
// w4.Trace(strconv.Itoa(int(p.ShootDirX)))
// w4.Trace(strconv.Itoa(int(p.ShootDirY)))
// w4.Trace(p.Position.String())
w4.Tone(150|50<<16, 10, 100, w4.TONE_MODE1)
p.ShootTimer = 15
actors = append(actors, bullet)
}
}
isJumping := *p.Gamepad&w4.BUTTON_DOWN == 0
p.Speed.Y = math.Min(4, p.Speed.Y+gravity)
if p.StickGrabbed != nil {
p.Speed.Y = 0
p.Speed.X = 0
// p.Position = p.PointGrabbed.Position
if *p.Gamepad&w4.BUTTON_LEFT != 0 {
p.Speed.X -= 1
}
if *p.Gamepad&w4.BUTTON_RIGHT != 0 {
p.Speed.X += 1
}
if *p.Gamepad&w4.BUTTON_UP != 0 {
p.Speed.Y -= 1
}
if *p.Gamepad&w4.BUTTON_DOWN != 0 {
p.Speed.Y += 1
p.Speed.Y = 0
if *p.Gamepad&w4.BUTTON_1 == 0 {
p.Speed.X = float64(dirX)
p.Speed.Y = float64(dirY)
}
p.MoveOnRope(p.Speed)
if p.StickOffset < 0 {
@ -62,9 +87,9 @@ func (p *Player) Update() {
}
p.Position = p.StickGrabbed.GetPosition(p.StickOffset)
if lastGamepad&w4.BUTTON_2 != 0 {
p.GrabTimeout = 10
p.GrabTimer = 10
if isJumping {
p.GrabTimeout = 10
p.GrabTimer = 10
p.Speed = p.Speed.MulScalar(2)
if p.Speed.Y <= 0 {
p.Speed.Y -= 1 * 2
@ -75,9 +100,17 @@ func (p *Player) Update() {
}
p.StickGrabbed = nil
}
if p.ShootTimer == 15 {
impulse := Vector{-float64(dirX), -float64(dirY)}
if p.StickGrabbed != nil {
p.StickGrabbed.PointA.Position.MoveVec(impulse)
p.StickGrabbed.PointB.Position.MoveVec(impulse)
}
}
} else {
p.Position.Move(p.Speed.X, p.Speed.Y)
if *p.Gamepad&w4.BUTTON_DOWN == 0 && p.GrabTimeout == 0 {
if *p.Gamepad&w4.BUTTON_DOWN == 0 && p.GrabTimer == 0 && p.Health > 0 {
distance := math.MaxFloat64
var selectedPoint *Point
for _, point := range points {
@ -98,7 +131,6 @@ func (p *Player) Update() {
}
}
if stickDistance < 4 {
w4.Trace(strconv.FormatFloat(stickDistance, 'f', 3, 64))
p.StickGrabbed = selectedStick
p.StickOffset = selectedStick.GetOffset(p.Position)
p.StickGrabbed.PointA.Position.MoveVec(p.Speed)
@ -107,6 +139,11 @@ func (p *Player) Update() {
}
}
p.Position.X = math.Min(math.Max(0, p.Position.X), 320)
if p.Position.Y > 320 && p.Health > 0 {
p.Health = 0
p.Death()
}
return false
}
func (p *Player) MoveOnRope(motion Vector) {
@ -117,5 +154,36 @@ func (p *Player) MoveOnRope(motion Vector) {
func (p *Player) Draw() {
*w4.DRAW_COLORS = 0x34
w4.Rect(int(p.Position.X)-4-camX, int(p.Position.Y)-4-camY, 8, 8)
drawX := int(p.Position.X) - 4 - camX
drawY := int(p.Position.Y) - 4 - camY
w4.Rect(drawX, drawY, 8, 8)
eyePosX := drawX + 4 + int(p.ShootDirX)
eyePosY := drawY + 4 - 2 + int(p.ShootDirY)
w4.Rect(eyePosX-2, eyePosY, 1, 2)
w4.Rect(eyePosX+1, eyePosY, 1, 2)
if pvp {
*w4.DRAW_COLORS = 0x4
w4.Text("P"+strconv.Itoa(int(p.Index+1)), drawX-3, drawY-10)
}
}
func (p *Player) TakeHit(from Actor) {
if p.Health > 0 {
if p.Health--; p.Health == 0 {
if bullet, ok := from.(*Bullet); ok {
p.StickGrabbed = nil
p.KilledBy = bullet.Owner
p.Death()
}
}
w4.Tone(250|200<<16, 15, 60, w4.TONE_PULSE1)
}
}
func (p *Player) Death() {
p.Speed.Y = -3
playersAlive--
music.MuteFor(30)
w4.Tone(600|200<<16, 15|15<<8, 30, w4.TONE_PULSE2)
}

View File

@ -9,7 +9,7 @@ type Point struct {
Position Vector
PreviousPosition Vector
IsLocked bool
TimeOffset uint64
TimeOffset uint16
Sticks []*Stick
}
@ -24,14 +24,15 @@ func (p *Point) GetMotion() Vector {
func (p *Point) Draw() {
*w4.DRAW_COLORS = 0x31
fr := (frame + p.TimeOffset) % 60
fr := (frame + uint64(p.TimeOffset)) % 60
if fr > 20 {
*w4.DRAW_COLORS = 0x32
}
if fr > 40 {
*w4.DRAW_COLORS = 0x34
}
if fr > 40 {
*w4.DRAW_COLORS = 0x32
}
w4.Oval(int(p.Position.X)-2-camX, int(p.Position.Y)-2-camY, 4, 4)
}
type Stick struct {
@ -129,7 +130,7 @@ func CreateRope(start Vector, end Vector, divisions int) ([]*Point, []*Stick) {
Position: pos,
PreviousPosition: pos,
IsLocked: (i == 0 || i == divisions),
TimeOffset: lightIndex * 153,
TimeOffset: uint16(lightIndex * 147),
}
lightIndex++
if i != 0 {