337 lines
8.9 KiB
Lua
337 lines
8.9 KiB
Lua
-- Invector, License MIT, Author Jordach
|
|
|
|
-- AI handling routines.
|
|
|
|
invector.ai = {}
|
|
|
|
invector.ai.difficulty = {
|
|
[1] = {tmin=22, tmax=23*2, frate=12},
|
|
[2] = {tmin=21, tmax=22*2, frate=11},
|
|
[3] = {tmin=20, tmax=21*2, frate=10},
|
|
[4] = {tmin=19, tmax=20*2, frate=9},
|
|
[5] = {tmin=18, tmax=19*2, frate=8},
|
|
[6] = {tmin=17, tmax=18*2, frate=7},
|
|
[7] = {tmin=16, tmax=17*2, frate=6},
|
|
[8] = {tmin=15, tmax=16*2, frate=5},
|
|
[9] = {tmin=14, tmax=15*2, frate=4},
|
|
[10] = {tmin=13, tmax=14*2, frate=3},
|
|
[11] = {tmin=12, tmax=13*2, frate=2},
|
|
[12] = {tmin=11, tmax=12*2, frate=1},
|
|
}
|
|
|
|
local controls_template = {
|
|
up = false,
|
|
down = false,
|
|
left = false,
|
|
right = false,
|
|
jump = false,
|
|
aux1 = false,
|
|
sneak = false,
|
|
LMB = false,
|
|
RMB = false,
|
|
}
|
|
|
|
invector.ai.known_node_targets = {}
|
|
|
|
function invector.ai.think(self)
|
|
local controls = table.copy(self._last_control)
|
|
|
|
local kart_pos = self.object:get_pos()
|
|
local node_pos = vector.new(
|
|
math.floor(kart_pos.x),
|
|
math.floor(kart_pos.y),
|
|
math.floor(kart_pos.z)
|
|
)
|
|
|
|
local kart_yaw = self.object:get_yaw()
|
|
local vel = self.object:get_velocity()
|
|
local thonk_timer_new = 1
|
|
|
|
controls.left = false
|
|
controls.right = false
|
|
controls.up = false
|
|
controls.jump = false
|
|
-- Prefer waypoints that are marked as track rather than off track
|
|
-- by lack of the node group.
|
|
local cross = 0
|
|
local function preferential_waypoints(pos, target_pos)
|
|
local area_min = vector.new(node_pos.x-32, node_pos.y-32, node_pos.z-32)
|
|
local area_max = vector.new(node_pos.x+32, node_pos.y+32, node_pos.z+32)
|
|
local node_target
|
|
-- Scan for the start/finish line first if it's up next
|
|
if invector.tracks[invector.current_track] == nil then
|
|
error("Track data missing?")
|
|
elseif invector.tracks[invector.current_track].track_num_waypoints == self._waypoint then
|
|
node_target = "invector:sector_marker"
|
|
else
|
|
node_target = "invector:waypoint_" .. self._waypoint + 1
|
|
end
|
|
|
|
local target_nodes = minetest.find_nodes_in_area(area_min, area_max, node_target, false)
|
|
-- Make a list of valid nodes
|
|
local node_targets = {}
|
|
for i, pos in pairs(target_nodes) do
|
|
local check_for_non_track = table.copy(pos)
|
|
check_for_non_track.y = check_for_non_track.y - 1
|
|
local node = minetest.get_node_or_nil(check_for_non_track)
|
|
if node == nil then -- Skip nil nodes
|
|
else
|
|
local node_def = minetest.registered_nodes[node.name]
|
|
if node_def.groups == nil then
|
|
elseif node_def.groups.track then
|
|
table.insert(node_targets, table.copy(pos))
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Select a node to navigate to from the known good list
|
|
-- Prioritise the closest waypoint that's on track
|
|
local best_pos = {}
|
|
local best_dist
|
|
for k, v in pairs(node_targets) do
|
|
local dist = math.abs(solarsail.util.functions.pos_to_dist(kart_pos, v))
|
|
|
|
if best_dist == nil or dist < best_dist then
|
|
best_dist = dist
|
|
best_pos = k
|
|
end
|
|
end
|
|
|
|
--local test_pos = node_targets[best_pos] --node_targets[math.random(1, #node_targets)]
|
|
local test_pos = node_targets[best_pos]
|
|
local x, z = solarsail.util.functions.yaw_to_vec(kart_yaw, 1)
|
|
local kart_forwards = vector.new(x, 0, z)
|
|
|
|
local cross
|
|
if test_pos == nil then
|
|
else
|
|
local delta = vector.normalize(vector.subtract(test_pos, kart_pos))
|
|
cross = vector.cross(delta, kart_forwards)
|
|
|
|
--[[
|
|
minetest.add_particle({
|
|
pos = test_pos,
|
|
velocity = vector.new(0, 2, 0),
|
|
expiration_time = 3,
|
|
size = 3,
|
|
collisiondetection = false,
|
|
vertical = false,
|
|
texture = "invector_kart_shield.png",
|
|
glow = 14
|
|
})
|
|
]]
|
|
|
|
if cross.y <= -0.15 then
|
|
controls.right = true
|
|
elseif cross.y >= 0.15 then
|
|
controls.left = true
|
|
end
|
|
|
|
if cross.y <= -0.5 then
|
|
controls.jump = true
|
|
elseif cross.y >= 0.5 then
|
|
controls.jump = true
|
|
end
|
|
|
|
-- Manage drifting controls based on direction while drifting
|
|
if self._is_drifting == -1 then
|
|
if cross.y <= -0.65 then
|
|
elseif cross.y <= -0.3 then
|
|
controls.left = false
|
|
controls.right = false
|
|
elseif cross.y <= -0.15 then
|
|
controls.left = true
|
|
controls.right = false
|
|
end
|
|
elseif self._is_drifting == 1 then
|
|
if cross.y >= 0.65 then
|
|
elseif cross.y >= 0.3 then
|
|
controls.left = false
|
|
controls.right = false
|
|
elseif cross.y >= 0.15 then
|
|
controls.left = false
|
|
controls.right = true
|
|
end
|
|
end
|
|
|
|
end
|
|
controls.up = true
|
|
|
|
return cross
|
|
end
|
|
|
|
-- Only steer when on the ground
|
|
if vel.y == 0 then
|
|
cross = preferential_waypoints()
|
|
end
|
|
|
|
-- Figure out when the next thinking step is based on AI params
|
|
local function thonk_timer()
|
|
local thonk_time =
|
|
math.random(
|
|
self._ai_reaction_timing.min,
|
|
self._ai_reaction_timing.max
|
|
)
|
|
return thonk_time / 100
|
|
end
|
|
thonk_timer_new = thonk_timer()
|
|
|
|
-- Check if there are walls or objects in front or beside the kart
|
|
-- that would block forwards trajectory, this function will override
|
|
-- preferential_waypoints and adds a second to the thonk_timer
|
|
local function eyesight()
|
|
local bonus_thonk_time = 0
|
|
local possible_coll = false
|
|
|
|
local lookx, lookz = solarsail.util.functions.yaw_to_vec(kart_yaw, 1)
|
|
local look_vec = vector.new(lookx, 0, lookz)
|
|
local kart_pos_new = table.copy(kart_pos)
|
|
kart_pos_new.y = kart_pos_new.y + 0.25
|
|
local center_look =
|
|
Raycast(kart_pos_new, vector.add(kart_pos_new, vector.multiply(look_vec, 1.25)), false, false)
|
|
|
|
local node_pos, node, node_def
|
|
-- Search to avoid collisions
|
|
if center_look == nil then
|
|
else
|
|
for pointed in center_look do
|
|
if pointed.type == "node" then
|
|
node_pos = table.copy(pointed.under)
|
|
if node_pos ~= nil then
|
|
node = minetest.get_node_or_nil(node_pos).name
|
|
node_def = minetest.registered_nodes[node]
|
|
end
|
|
if node_def.walkable == nil then
|
|
if pointed.type == "node" then
|
|
possible_coll = true
|
|
break
|
|
end
|
|
elseif node_def.walkable then
|
|
if pointed.type == "node" then
|
|
possible_coll = true
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Turn away from the possible collision for a short moment
|
|
if possible_coll then
|
|
bonus_thonk_time = math.random(50, 250) / 100
|
|
controls.up = false
|
|
controls.down = true
|
|
controls.jump = false
|
|
if cross == nil then
|
|
else
|
|
if cross.y > 0 then
|
|
controls.left = false
|
|
controls.right = true
|
|
elseif cross.y < 0 then
|
|
controls.right = false
|
|
controls.left = true
|
|
end
|
|
end
|
|
end
|
|
|
|
return thonk_timer_new+bonus_thonk_time
|
|
end
|
|
if vel.y == 0 then
|
|
thonk_timer_new = eyesight()
|
|
end
|
|
|
|
-- Always reset controls
|
|
controls.LMB = false
|
|
controls.RMB = false
|
|
local function use_items()
|
|
if self._held_item > 0 then
|
|
if self._held_item == 1 then -- TNT
|
|
if math.random(0, 99) > 5 then -- Use chance
|
|
if math.random(0, 99) > 75 then -- Fire forwards or backwards
|
|
controls.LMB = true
|
|
else
|
|
controls.RMB = true
|
|
end
|
|
end
|
|
elseif self._held_item == 2 then -- Prox TNT
|
|
if math.random(0, 99) > 5 then -- Use chance
|
|
if math.random(0, 99) > 85 then -- Fire forwards or backwards
|
|
controls.LMB = true
|
|
else
|
|
controls.RMB = true
|
|
end
|
|
end
|
|
elseif self._held_item == 3 then -- Rocket
|
|
if math.random(0, 99) > 5 then -- Use chance
|
|
if math.random(0, 99) < 75 then -- Fire forwards or backwards
|
|
controls.LMB = true
|
|
else
|
|
controls.RMB = true
|
|
end
|
|
end
|
|
elseif self._held_item == 4 then -- Missile
|
|
if math.random(0, 99) > 5 then -- Use chance
|
|
if math.random(0, 99) < 85 then -- Fire forwards or backwards
|
|
controls.LMB = true
|
|
else
|
|
controls.RMB = true
|
|
end
|
|
end
|
|
elseif self._held_item == 5 then -- Shield
|
|
if not self._shields then
|
|
controls.LMB = true
|
|
end
|
|
elseif self._held_item == 6 then -- Ion Cannon
|
|
if math.random(0, 99) > 5 then -- Use chance
|
|
if math.random(0, 99) > 75 then -- Fire forwards or backwards
|
|
controls.LMB = true
|
|
else
|
|
controls.RMB = true
|
|
end
|
|
end
|
|
elseif self._held_item == 7 then -- Sand
|
|
if math.random(0, 99) > 5 then -- Use chance
|
|
if math.random(0, 99) < 75 then -- Fire forwards or backwards
|
|
controls.LMB = true
|
|
else
|
|
controls.RMB = true
|
|
end
|
|
end
|
|
elseif self._held_item == 8 then -- Mese Crystal
|
|
if self._boost_timer <= 0 then
|
|
controls.LMB = true
|
|
end
|
|
elseif self._held_item == 9 then -- Mese Shards
|
|
if math.random(0, 99) > 5 then -- Use chance
|
|
if math.random(0, 99) > 75 then -- Fire forwards or backwards
|
|
controls.LMB = true
|
|
else
|
|
controls.RMB = true
|
|
end
|
|
end
|
|
elseif self._held_item == 10 then -- Nanite Boosters
|
|
if self._boost_timer <= 0 then
|
|
controls.LMB = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
-- Only use items when on the ground
|
|
if vel.y == 0 then
|
|
use_items()
|
|
end
|
|
|
|
-- Only apply control modulation when on the ground
|
|
local result_controls = {}
|
|
if vel.y == 0 then
|
|
for k, v in pairs(controls) do
|
|
if math.random(1,100) <= self._ai_button_press_success then
|
|
if v then -- Quickly invert keys when the AI makes a "mistake"
|
|
controls[k] = false
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return controls, thonk_timer_new
|
|
end |