324 lines
9.5 KiB
Odin
324 lines
9.5 KiB
Odin
package main
|
||
|
||
import rl "vendor:raylib"
|
||
import "vendor:raylib/rlgl"
|
||
import "core:math"
|
||
import "core:fmt"
|
||
import "core:math/linalg"
|
||
import "core:math/rand"
|
||
|
||
|
||
Snake_Health := 1000
|
||
|
||
Segments: [dynamic]^SnakeSegment
|
||
|
||
Head: SnakeHead
|
||
|
||
SnakeState :: enum {
|
||
Chasing,
|
||
Chase_to_Dive,
|
||
Dive_to_Chase,
|
||
Diving,
|
||
Hunt,
|
||
Shot
|
||
}
|
||
|
||
SnakeHead :: struct {
|
||
pos: vec3,
|
||
vel: vec3,
|
||
dir: f32,
|
||
radius: f32,
|
||
health: int,
|
||
max_health: int,
|
||
next: ^SnakeSegment,
|
||
state: SnakeState,
|
||
next_state: SnakeState,
|
||
state_timer: f32,
|
||
shot_timer: f32,
|
||
is_shooting: bool,
|
||
is_dead: bool,
|
||
}
|
||
|
||
SnakeSegment :: struct {
|
||
pos: vec3,
|
||
vel: vec3,
|
||
dir: f32,
|
||
radius: f32,
|
||
collider_radius: f32,
|
||
active: bool,
|
||
health: u8,
|
||
head: ^SnakeHead,
|
||
next: ^SnakeSegment,
|
||
prev: ^SnakeSegment
|
||
}
|
||
|
||
|
||
snake_spawn :: proc(pos: vec3, dir: f32, length: int){
|
||
dir_vec := rl.Vector3RotateByAxisAngle(vec3right, vec3backward, dir)
|
||
|
||
Head = SnakeHead{
|
||
pos = pos,
|
||
dir = dir,
|
||
radius = 3,
|
||
state = .Chasing,
|
||
vel = dir_vec * 20,
|
||
health = 100,
|
||
max_health = 100,
|
||
state_timer = 30,
|
||
}
|
||
|
||
for i := 0; i < length; i += 1 {
|
||
segment := new(SnakeSegment)
|
||
segment.active = false
|
||
segment.health = 3
|
||
segment.radius = 2.5
|
||
segment.collider_radius = 2.5
|
||
segment.pos = pos - dir_vec * segment.radius
|
||
segment.dir = dir
|
||
segment.head = &Head
|
||
if i != 0 {
|
||
segment.prev = Segments[i-1]
|
||
Segments[i-1].next = segment
|
||
} else {
|
||
Head.next = segment
|
||
}
|
||
append(&Segments, segment)
|
||
}
|
||
}
|
||
|
||
snake_clear :: proc() {
|
||
for segment in Segments {
|
||
free(segment)
|
||
}
|
||
}
|
||
|
||
snake_process :: proc(game: ^Game, delta: f32) {
|
||
// Head.state = .Shot
|
||
// Head.state_timer = 200
|
||
switch Head.state {
|
||
case .Chasing:
|
||
snake_chase_smooth(game, delta)
|
||
case .Diving:
|
||
snake_dive(game, delta)
|
||
case .Chase_to_Dive,.Dive_to_Chase:
|
||
snake_dropdown(game, delta)
|
||
case .Hunt:
|
||
snake_hunt(game, delta)
|
||
case .Shot:
|
||
snake_shot(game, delta)
|
||
}
|
||
total_health := 0
|
||
for segment in Segments {
|
||
total_health += int(segment.health)
|
||
}
|
||
game.snake_health = total_health
|
||
if game.snake_health == 0 {
|
||
if Head.next != nil {
|
||
Head.next = nil
|
||
Head.state = .Hunt
|
||
Head.state_timer = 20
|
||
change_track(Res.Music.Second)
|
||
return
|
||
}
|
||
trail(Head.pos - get_vec_from_angle(Head.dir) * Head.radius * 0.7, 3, rand.float32_range(0.7, 1.8), rl.RED)
|
||
if Head.health <= 0 && !Head.is_dead {
|
||
Head.is_dead = true
|
||
explode(Head.pos, 9, 0.9, rl.YELLOW)
|
||
rl.PlaySound(Res.Sfx.PlayerDead)
|
||
timer_start(3, game, proc(data: rawptr) {
|
||
state := transmute(^Game)data
|
||
win := winning_init(state)
|
||
stack_push(win)
|
||
})
|
||
}
|
||
}
|
||
for segment, i in Segments {
|
||
if segment.prev == nil && total_health == 0 { // Хвост падает, когда у него не осталось жизней
|
||
segment.vel.y -= 30 * delta
|
||
segment.pos += segment.vel * delta
|
||
continue
|
||
}
|
||
|
||
target_pos := Head.pos
|
||
if segment.prev != nil {
|
||
target_pos = segment.prev.pos
|
||
} else {
|
||
segment.vel = Head.vel
|
||
}
|
||
diff := target_pos - segment.pos
|
||
if rl.Vector3Length(diff) > segment.radius {
|
||
segment.pos = target_pos - rl.Vector3Normalize(diff) * segment.radius
|
||
}
|
||
segment.dir = math.atan2(-diff.y, diff.x)
|
||
}
|
||
}
|
||
|
||
|
||
snake_chase :: proc(game: ^Game, delta: f32) {
|
||
using game
|
||
diff := player.pos - Head.pos
|
||
target_angle := math.atan2(-diff.y, diff.x)
|
||
dir_diff := angle_cycle(target_angle - Head.dir, -math.PI, math.PI)
|
||
Head.dir = angle_rotate(Head.dir, target_angle, min(abs(dir_diff), math.PI) * delta)
|
||
Head.vel = rl.Vector3RotateByAxisAngle(vec3right, vec3backward, Head.dir) * 500
|
||
Head.pos += Head.vel * delta
|
||
}
|
||
|
||
snake_chase_smooth :: proc(game: ^Game, delta: f32) {
|
||
using game
|
||
|
||
if rl.IsKeyPressed(rl.KeyboardKey.K) {
|
||
for segment in Segments {
|
||
segment.health = 0
|
||
}
|
||
}
|
||
|
||
Head.state_timer -= delta
|
||
if Head.state_timer <= 0 {
|
||
Head.state = .Chase_to_Dive
|
||
rl.PlaySound(Res.Sfx.SnakeGrowl)
|
||
Head.next_state = .Diving
|
||
Head.state_timer = 10
|
||
}
|
||
|
||
if Head.pos.y < -3 && Head.vel.y < 0 {
|
||
Head.pos.y = -3
|
||
Head.vel.y = -Head.vel.y
|
||
}
|
||
|
||
diff := player.pos - Head.pos
|
||
norm := rl.Vector3Normalize(diff)
|
||
Head.vel = rl.Vector3MoveTowards(Head.vel, norm * 70, 70 * delta)
|
||
Head.vel = rl.Vector3ClampValue(Head.vel, 30, 50)
|
||
Head.dir = math.atan2(-Head.vel.y, Head.vel.x)
|
||
Head.pos += Head.vel * delta
|
||
}
|
||
|
||
snake_dive :: proc(game: ^Game, delta: f32) {
|
||
Head.state_timer -= delta
|
||
if Head.state_timer <= 0 {
|
||
Head.state = .Chasing
|
||
Head.state_timer = 20
|
||
rl.PlaySound(Res.Sfx.SnakeGrowl)
|
||
}
|
||
if Head.pos.y < 0 && Segments[len(Segments)-1].pos.y < 0 && Head.vel.y < 0 {
|
||
rl.PlaySound(Res.Sfx.SnakeEarthHit)
|
||
Head.pos.x = game.player.pos.x
|
||
Head.pos.y = -5
|
||
Head.vel.x = 0
|
||
Head.vel.y = 70
|
||
} else {
|
||
grav : f32 = 20
|
||
if Head.vel.y < 0 { grav = 30 }
|
||
else if Head.pos.y < game.player.pos.y {
|
||
grav = 7
|
||
}
|
||
Head.vel.y -= grav * delta
|
||
Head.vel.x = (game.player.pos.x - Head.pos.x) * 2
|
||
}
|
||
Head.dir = math.atan2(-Head.vel.y, Head.vel.x)
|
||
Head.pos += Head.vel * delta
|
||
}
|
||
|
||
snake_dropdown :: proc(game: ^Game, delta: f32) {
|
||
Head.vel.y -= 100 * delta
|
||
Head.pos += Head.vel * delta
|
||
Head.dir = math.atan2(-Head.vel.y, Head.vel.x)
|
||
if Segments[len(Segments)-1].pos.y < 0 {
|
||
Head.state = Head.next_state
|
||
Head.state_timer = 20
|
||
}
|
||
}
|
||
|
||
snake_hunt :: proc(game: ^Game, delta: f32) {
|
||
Head.state_timer -= delta
|
||
if Head.state_timer <= 0 {
|
||
Head.state_timer = 10
|
||
Head.state = .Shot
|
||
rl.PlaySound(Res.Sfx.SnakeRoarBlast)
|
||
}
|
||
diff := game.player.pos - Head.pos
|
||
target_pos := game.player.pos + rl.Vector3Normalize(diff) * 10
|
||
target_diff := target_pos - Head.pos
|
||
|
||
Head.vel = rl.Vector3MoveTowards(Head.vel, rl.Vector3Normalize(target_diff) * 50, 40 * delta)
|
||
Head.dir = math.atan2(-Head.vel.y, Head.vel.x)
|
||
Head.pos += Head.vel * delta
|
||
}
|
||
|
||
snake_shot :: proc(game: ^Game, delta: f32) {
|
||
Head.state_timer -= delta
|
||
if Head.state_timer <= 0 {
|
||
Head.state_timer = 20
|
||
Head.state = .Hunt
|
||
}
|
||
Head.is_shooting = false
|
||
if Head.state_timer < 8 && Head.state_timer > 3 {
|
||
Head.is_shooting = true
|
||
if !rl.IsSoundPlaying(Res.Sfx.SnakeBeam) {
|
||
rl.PlaySound(Res.Sfx.SnakeBeam)
|
||
}
|
||
}
|
||
|
||
diff := game.player.pos - Head.pos
|
||
target_angle := math.atan2(-diff.y, diff.x)
|
||
dir_diff := angle_cycle(target_angle - Head.dir, -math.PI, math.PI)
|
||
Head.dir = angle_rotate(Head.dir, target_angle, abs(dir_diff) * delta * 1.5)
|
||
Head.vel = rl.Vector3MoveTowards(Head.vel, {}, 30 * delta)
|
||
Head.pos += Head.vel * delta
|
||
}
|
||
|
||
|
||
snake_draw :: proc(game: ^Game) {
|
||
|
||
dir_vector := get_vec_from_angle(Head.dir)
|
||
roll := -math.PI / 2 + math.cos(Head.dir) * math.PI / 2
|
||
rlgl.PushMatrix()
|
||
rlgl.Translatef(Head.pos.x, Head.pos.y, Head.pos.z)
|
||
rlgl.Rotatef(math.to_degrees(Head.dir), 0, 0, -1)
|
||
rlgl.Rotatef(math.to_degrees(roll), 1, 0, 0)
|
||
// rl.DrawCircle3D({}, Head.radius, vec3up, 0, rl.RED)
|
||
// rl.DrawLine3D({}, {-4, 0, 0}, rl.GREEN)
|
||
rl.DrawModel(Res.Models.SnakeHeadTop, {}, 5, rl.WHITE)
|
||
rlgl.PushMatrix()
|
||
if Head.state == .Shot {
|
||
step1 := math.smoothstep(f32(10), f32(8), f32(Head.state_timer))
|
||
step2 := math.smoothstep(f32(0), f32(3), f32(Head.state_timer))
|
||
angle := step1 * step2 * math.PI / 2.5
|
||
fmt.println(Head.state_timer, step1, step2, angle)
|
||
rlgl.Rotatef(math.to_degrees(angle), 0, 0, -1)
|
||
}
|
||
rl.DrawModel(Res.Models.SnakeHeadJaw, {}, 5, rl.WHITE)
|
||
rlgl.PopMatrix()
|
||
rlgl.Translatef(0.5, -1, 0)
|
||
if Head.is_shooting {
|
||
rl.DrawCylinderEx({}, vec3right * 300, 1.4, 1.4, 6, rl.YELLOW)
|
||
}
|
||
rlgl.PopMatrix()
|
||
// rl.DrawCircle3D(Head.pos, Head.radius, vec3up, 0, rl.RED)
|
||
// rl.DrawLine3D(Head.pos, Head.pos + dir_vector * Head.radius, rl.BLACK)
|
||
|
||
|
||
// if game.snake_health == 0 {
|
||
// center := Head.pos + dir_vector * Head.radius * 1.5
|
||
// left := Head.pos + rl.Vector3RotateByAxisAngle(dir_vector, vec3backward, math.PI/3) * Head.radius * 1.5
|
||
// right := Head.pos + rl.Vector3RotateByAxisAngle(dir_vector, vec3backward, -math.PI/3) * Head.radius * 1.5
|
||
// rl.DrawLine3D(left, center, rl.YELLOW)
|
||
// rl.DrawLine3D(right, center, rl.YELLOW)
|
||
// }
|
||
|
||
for segment in Segments {
|
||
dir_vector := get_vec_from_angle(segment.dir)
|
||
col := rl.WHITE
|
||
if segment.health == 0 {
|
||
col = rl.Color{255, 150, 150, 255}
|
||
}
|
||
roll := -math.PI / 2 + math.cos(segment.dir) * math.PI / 2
|
||
rlgl.PushMatrix()
|
||
rlgl.Translatef(segment.pos.x, segment.pos.y, segment.pos.z)
|
||
rlgl.Rotatef(math.to_degrees(segment.dir), 0, 0, -1)
|
||
rlgl.Rotatef(math.to_degrees(roll), 1, 0, 0)
|
||
rl.DrawModel(Res.Models.SnakeBody, {}, 4, col)
|
||
rlgl.PopMatrix()
|
||
}
|
||
} |