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 if Head.pos.y < Head.radius { Head.pos.y = Head.radius Head.vel.y = -Head.vel.y / 2 } } 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() } }