Ragnarokkr/snake.odin

263 lines
6.9 KiB
Odin
Raw Normal View History

2024-10-04 21:16:58 +03:00
package main
import rl "vendor:raylib"
import "core:math"
import "core:fmt"
import "core:math/linalg"
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 = 5,
}
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) {
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 && Head.next != nil {
Head.next = nil
Head.state = .Hunt
Head.state_timer = 20
fmt.println("Tail is dead")
return
}
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
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 = .Dive_to_Chase
Head.next_state = .Chasing
}
if Head.pos.y < 0 && Segments[len(Segments)-1].pos.y < 0 && Head.vel.y < 0 {
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
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
}
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)
fmt.println(Head.vel)
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
}
diff := game.player.pos - Head.pos
angle := math.atan2(-diff.y, diff.x)
Head.dir = angle_rotate(Head.dir, angle, angle_cycle(abs(angle - Head.dir), -math.PI, math.PI) * delta * 2)
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)
rl.DrawCircle3D(Head.pos, Head.radius, vec3up, 0, rl.RED)
rl.DrawLine3D(Head.pos, Head.pos + dir_vector * Head.radius, rl.BLACK)
if Head.is_shooting {
rl.DrawLine3D(Head.pos, Head.pos + get_vec_from_angle(Head.dir) * 300, rl.YELLOW)
}
for segment in Segments {
dir_vector := get_vec_from_angle(segment.dir)
col := rl.RED
if segment.health == 0 {
col = rl.GRAY
}
rl.DrawCircle3D(segment.pos, segment.radius, vec3up, 0, col)
rl.DrawLine3D(segment.pos, segment.pos + dir_vector * segment.radius, rl.BLACK)
}
}