oma-tank/game/player.lua
2026-04-18 11:35:06 +01:00

120 lines
3.5 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

local World = require("game.world")
local Player = {}
-- Tank-feel constants tuned for the authentic 1980 arcade rhythm: deliberate
-- movement, slow rotation, one-track drive is a partial speed boost.
local MAX_SPEED = 60
local ROT_SPEED = 0.5 -- ~28°/s — deliberate but playable
local ONE_TRACK_BONUS = 1.5 -- one-track-only drives at 1.5× the average
local COLLISION_RADIUS = 12
local MOVE_EPSILON = 0.02
local BLOCKED_MSG_DURATION = 0.8
local player = {
x = 0, z = 0,
angle = 0,
alive = true,
invulnTimer = 0,
deathTimer = 0,
blockedMessageTimer = 0,
blockedJustTriggered = false,
_wasBlocked = false,
}
function Player.init()
player.x = World.FIELD_SIZE / 2
player.z = World.FIELD_SIZE / 2
player.angle = 0
player.alive = true
player.invulnTimer = 0 -- no respawn-invuln; the scene resets around the player
player.deathTimer = 0
player.blockedMessageTimer = 0
player.blockedJustTriggered = false
player._wasBlocked = false
end
function Player.get()
return player
end
function Player.update(dt, obstacles, leftTrack, rightTrack)
player.blockedMessageTimer = math.max(0, player.blockedMessageTimer - dt)
player._moving = false
if not player.alive then
player.deathTimer = player.deathTimer + dt
return
end
player.invulnTimer = math.max(0, player.invulnTimer - dt)
leftTrack = leftTrack or 0
rightTrack = rightTrack or 0
-- Rotation (opposing tracks): left track forward + right track back => turn right
local turn = (leftTrack - rightTrack) * 0.5
player.angle = player.angle + turn * ROT_SPEED * dt
-- Forward/reverse (average of tracks), with one-track-only speed bonus
local drive = (leftTrack + rightTrack) * 0.5
local leftActive = math.abs(leftTrack) > MOVE_EPSILON
local rightActive = math.abs(rightTrack) > MOVE_EPSILON
if (leftActive and not rightActive) or (rightActive and not leftActive) then
drive = drive * ONE_TRACK_BONUS
end
local blocked = false
if math.abs(drive) > MOVE_EPSILON then
player._moving = true
local speed = drive * MAX_SPEED
-- Forward vector: (sin(angle), cos(angle)); angle=0 → +z
local nx = player.x + math.sin(player.angle) * speed * dt
local nz = player.z + math.cos(player.angle) * speed * dt
nx, nz = World.wrapField(nx, nz)
if obstacles then
for _, o in ipairs(obstacles) do
local dx = World.wrappedDelta(nx, o.x, World.FIELD_SIZE)
local dz = World.wrappedDelta(nz, o.z, World.FIELD_SIZE)
local dist = math.sqrt(dx*dx + dz*dz)
if dist < COLLISION_RADIUS + o.radius then
blocked = true
break
end
end
end
if not blocked then
player.x = nx
player.z = nz
end
end
-- Fire the "MOTION BLOCKED" message once per sustained block, not every frame
if blocked and not player._wasBlocked then
player.blockedMessageTimer = BLOCKED_MSG_DURATION
player.blockedJustTriggered = true
end
player._wasBlocked = blocked
end
function Player.die()
player.alive = false
player.deathTimer = 0
end
function Player.respawn()
player.x = World.FIELD_SIZE / 2
player.z = World.FIELD_SIZE / 2
player.alive = true
player.invulnTimer = 0
player.deathTimer = 0
end
function Player.isMoving()
return player._moving == true
end
return Player