cars/init.lua

763 lines
23 KiB
Lua

local go = false
local DEBUG_WAYPOINT = false
local DEBUG_TEXT = false
local function get_sign(i)
if i == 0 then
return 0
else
return i / math.abs(i)
end
end
local player_attached = {}
local attachTimer = 0
local animateTimer = 0
minetest.register_globalstep(function(dtime)
attachTimer = attachTimer + dtime;
animateTimer = animateTimer + dtime
if attachTimer >= 5 then
minetest.after(0, function() attachTimer = 0 end)
end
if animateTimer >= .08 then
minetest.after(0, function() animateTimer = 0 end)
end
end)
local function detach(player)
if not player then return end
local name = player:get_player_name()
if not name then return end
local attached = player_attached[name]
if not attached then return end
player_attached[name] = nil
local i = 0
while i <= #attached.passengers do
i = i + 1
if attached.passengers[i].player == player then
attached.passengers[i].player = nil
if i == 1 then player:hud_remove(attached.hud) end
break
end
end
player:set_detach()
player:set_eye_offset({x=0,y=0,z=0}, {x=0,y=0,z=0})
default.player_attached[name] = false
default.player_set_animation(player, "stand" , 30)
end
local function get_yaw(yaw)
local sign = get_sign(yaw)
local newyaw = math.abs(yaw)
while newyaw > math.pi do
newyaw = newyaw - math.pi
end
return newyaw*sign
end
local function get_velocity(v, yaw, velocity)
local x = -math.sin(yaw) * v
local z = math.cos(yaw) * v
return {x = x, y = velocity.y, z = z}
end
local function get_v(v)
return math.sqrt(v.x ^ 2 + v.z ^ 2)
end
local function serializeContents(contents)
if not contents then return "" end
local tabs = {}
for i, stack in ipairs(contents) do
tabs[i] = stack and stack:to_table() or ""
end
return minetest.serialize(tabs)
end
local function deserializeContents(data)
if not data or data == "" then return nil end
local tabs = minetest.deserialize(data)
if not tabs or type(tabs) ~= "table" then return nil end
local contents = {}
for i, tab in ipairs(tabs) do
contents[i] = ItemStack(tab)
end
return contents
end
local charset = {} do -- [A-Z]
for c = 65, 90 do table.insert(charset, string.char(c)) end
end
local numset = {} do -- [0-9]
for c = 48, 57 do table.insert(numset, string.char(c)) end
end
local function randomString(length)
local text = ""
local i = 0
if not length or length <= 0 then return text end
while i < length do
text = text..charset[math.random(1, #charset)]
i = i + 1
end
return text
end
local function randomNumber(length)
local text = ""
local i = 0
if not length or length <= 0 then return text end
while i < length do
text = text..numset[math.random(1, #numset)]
i = i + 1
end
return text
end
local function wheelspeed(car)
if not car then return end
if not car.object then return end
if not car.object:getvelocity() then return end
if not car.wheel then return end
local direction = 1
if car.v then
direction = get_sign(car.v)
end
local v = get_v(car.object:get_velocity())
local fps = v*4
for id, wheel in pairs(car.wheel) do
wheel:set_animation({x=2, y=9}, fps*direction, 0, true)
end
if v ~= 0 then
local i = 16
while true do
if i/fps > 1 then i = i/2 else break end
end
minetest.after(i/fps, wheelspeed, car)
end
end
local function rotateVector(x, y, a)
local c = math.cos(a)
local s = math.sin(a)
return c*x - s*y, s*x + c*y
end
local function getClosest(player, car)
local def = cars_registered_cars[car.object:get_entity_name()]
local playerPos = player:getpos()
local dir = player:get_look_dir()
playerPos.y = playerPos.y + 1.45
local carPos = car.object:getpos()
local offset, _ = player:get_eye_offset()
local playeryaw = player:get_look_horizontal()
local x, z = rotateVector(offset.x, offset.z, playeryaw)
offset = vector.multiply({x=x, y=offset.y, z=z}, .1)
playerPos = vector.add(playerPos, offset)
if DEBUG_WAYPOINT then
local marker = player:hud_add({
hud_elem_type = "waypoint",
name = "start",
number = 0xFF0000,
world_pos = playerPos
})
minetest.after(5, function() player:hud_remove(marker) end, player, marker)
end
local punchPos = vector.add(playerPos, vector.multiply(dir, vector.distance(playerPos, carPos)))
if minetest.raycast then
local ray = minetest.raycast(playerPos, vector.add(playerPos, vector.multiply(dir, vector.distance(playerPos, carPos))))
if ray then
local pointed = ray:next()
if pointed and pointed.ref == player then
pointed = ray:next()
end
if pointed and pointed.ref == car.object and pointed.intersection_point then
punchPos = pointed.intersection_point
end
end
end
if not punchPos then return end
if DEBUG_WAYPOINT then
local marker = player:hud_add({
hud_elem_type = "waypoint",
name = "end",
number = 0xFF0000,
world_pos = punchPos
})
minetest.after(5, function() player:hud_remove(marker) end, player, marker)
end
punchPos = vector.subtract(punchPos, carPos)
local carYaw = car.object:getyaw()
local closest = {}
closest.id = 0
local trunkloc = def.trunkloc
if trunkloc then
local x, z = rotateVector(trunkloc.x, trunkloc.z, carYaw)
trunkloc = vector.multiply({x=x, y=trunkloc.y, z=z}, .1)
closest.distance = vector.distance(punchPos, trunkloc)
end
if DEBUG_WAYPOINT then
local marker = player:hud_add({
hud_elem_type = "waypoint",
name = "0",
number = 0xFF0000,
world_pos = vector.add(trunkloc, carPos)
})
minetest.after(5, function() player:hud_remove(marker) end, player, marker)
end
for id in pairs(car.passengers) do
local loc = car.passengers[id].loc
local x, z = rotateVector(loc.x, loc.z, carYaw)
loc = vector.multiply({x=x, y=loc.y, z=z}, .1)
if DEBUG_WAYPOINT then
local marker = player:hud_add({
hud_elem_type = "waypoint",
name = id,
number = 0xFF0000,
world_pos = vector.add(loc, carPos)
})
minetest.after(5, function() player:hud_remove(marker) end, player, marker)
end
local dis = vector.distance(punchPos, loc)
if not closest.distance then
closest.distance = dis
closest.id = id
elseif dis < closest.distance then closest.id = id closest.distance = dis end
end
return closest.id
end
local trunkplayer = {}
local function trunk_rightclick(self, clicker)
local def = cars_registered_cars[self.object:get_entity_name()]
local name = clicker:get_player_name()
trunkplayer[name] = self
local selfname = string.sub(tostring(self), 8)
local inventory = minetest.create_detached_inventory("cars"..selfname, {
on_move = function(inv, from_list, from_index, to_list, to_index, count, player)
self.trunkinv = inv:get_list("trunk")
end,
on_put = function(inv, listname, index, stack, player)
self.trunkinv = inv:get_list("trunk")
end,
on_take = function(inv, listname, index, stack, player)
self.trunkinv = inv:get_list("trunk")
end,
})
local x = def.trunksize.x
local y = def.trunksize.y
local formx = x
inventory:set_size("trunk", x * y)
if x < 8 then formx = 8 end
local templist = table.copy(self.trunkinv)
inventory:set_list("trunk", templist)
local formspec =
"size["..formx..","..5+y.."]"..
"list[detached:cars"..selfname..";trunk;0,.5;"..x..","..y..";]"..
"list[current_player;main;0,"..1+y..";8,4;]"
minetest.show_formspec(name, "cars_trunk", formspec)
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname == "cars_trunk" then
if fields.quit then
local name = player:get_player_name()
if trunkplayer[name] then
minetest.sound_play("closetrunk", {
max_hear_distance = 24,
gain = 1,
object = trunkplayer[name].object
})
trunkplayer[name] = nil
end
end
end
end)
local function car_step(self, dtime)
local def = cars_registered_cars[self.object:get_entity_name()]
if not self.v then self.v = 0 end
self.v = get_v(self.object:getvelocity()) * get_sign(self.v)
local pos = self.object:getpos()
if self.lastv then
local newv = self.object:getvelocity()
if not self.crash then self.crash = false end
local crash = false
if math.abs(self.lastv.x) > 5 and newv.x == 0 then crash = true end
if math.abs(self.lastv.y) > 10 and newv.y == 0 then crash = true end
if math.abs(self.lastv.z) > 5 and newv.z == 0 then crash = true end
--[[if crash then
local start = {x=pos.x, y=pos.y+self.stepheight, z=pos.z}
local finish = vector.add(start, vector.multiply(vector.normalize(self.lastv), 1))
if minetest.raycast then
local ray = minetest.raycast(start, finish)
if ray then
local pointed = ray:next()
if pointed == self then
pointed = ray:next()
end
--minetest.chat_send_all(dump(pointed.ref))
minetest.add_particle({
pos = finish,
expirationtime = 10,
size = 2,
texture = "gunslinger_decal.png",
vertical = true
})
if not pointed then crash = false end
end
else
if minetest.get_node(finish).name == "air" then crash = false end
end
end--]]
if crash and not self.crash then
self.crash = true
minetest.after(.5, function()
self.crash = false
end)
minetest.sound_play("crash"..math.random(1,3), {
max_hear_distance = 48,
pitch = .7,
gain = 10,
object = self.object
})
local checkpos = vector.add(pos, vector.multiply(vector.normalize(self.lastv), .8))
local objects = minetest.get_objects_inside_radius(checkpos, 1)
for _,obj in pairs(objects) do
if obj:is_player() then
for id, passengers in pairs (self.passengers) do
if passengers.player == obj then goto next end
end
local puncher = self.passengers[1].player
if not puncher then puncher = self.object end
local dmg = ((vector.length(self.lastv)-4)/(20-4))*20
local name = obj:get_player_name()
if default.player_attached[name] then dmg = dmg*.5 end
obj:punch(puncher, nil, {damage_groups={fleshy=dmg}})
::next::
end
end
end
end
local driver = self.passengers[1].player
if driver then
driver:hud_change(self.hud, "text", tostring(math.abs(math.floor(self.v*2.23694*10)/10)).." MPH")
local ctrl = driver:get_player_control()
local yaw = self.object:getyaw()
local sign
if self.v == 0 then sign = 0 else sign = get_sign(self.v) end
if ctrl.up then
if sign >= 0 then
self.v = self.v + def.acceleration*dtime
else
self.v = self.v + def.braking*dtime
end
elseif ctrl.down then
if sign <= 0 then
self.v = self.v - def.acceleration*dtime
else
self.v = self.v - def.braking*dtime
end
elseif sign ~= 0 then
self.v = self.v - def.coasting*dtime*get_sign(self.v)
end
if get_sign(self.v) ~= sign and sign ~= 0 then
self.v = 0
end
local abs_v = math.abs(self.v)
local maxwheelpos = 45*(8/(abs_v+8))
if ctrl.left and self.wheelpos <= 0 then
self.wheelpos = self.wheelpos-50*dtime*(4/(abs_v+4))
if self.wheelpos < -1*maxwheelpos then
self.wheelpos = -1*maxwheelpos
end
elseif ctrl.right and self.wheelpos >= 0 then
self.wheelpos = self.wheelpos+50*dtime*(4/(abs_v+4))
if self.wheelpos > maxwheelpos then
self.wheelpos = maxwheelpos
end
else
local sign = get_sign(self.wheelpos)
self.wheelpos = self.wheelpos - 100*get_sign(self.wheelpos)*dtime
if math.abs(self.wheelpos) < 5 or sign ~= get_sign(self.wheelpos) then
self.wheelpos = 0
end
end
if animateTimer >= .08 then
self.wheel.frontright:set_attach(self.object, "", {z=10.75,y=2.5,x=-8.875}, {x=0,y=self.wheelpos,z=0})
self.wheel.frontleft:set_attach(self.object, "", {z=10.75,y=2.5,x=8.875}, {x=0,y=self.wheelpos,z=0})
self.steeringwheel:set_attach(self.object, "", {z=5.62706,y=8.25,x=-4.0}, {x=0,y=0,z=-self.wheelpos*8})
end
self.object:setyaw(yaw - ((self.wheelpos/8)*(self.v/8)*dtime))
if attachTimer >= 5 then
self.licenseplate:set_attach(self.object, "", {x = -.38, y = -0.85, z = -15.51}, {x = 0, y = 0, z = 0})
self.wheel.backright:set_attach(self.object, "", {z=-11.75,y=2.5,x=-8.875}, {x=0,y=0,z=0})
self.wheel.backleft:set_attach(self.object, "", {z=-11.75,y=2.5,x=8.875}, {x=0,y=0,z=0})
end
else
if math.abs(self.wheelpos) > 0 then
local yaw = self.object:getyaw()
self.wheelpos = 0
self.wheel.frontright:set_attach(self.object, "", {z=10.75,y=2.5,x=-8.875}, {x=0,y=self.wheelpos,z=0})
self.wheel.frontleft:set_attach(self.object, "", {z=10.75,y=2.5,x=8.875}, {x=0,y=self.wheelpos,z=0})
self.steeringwheel:set_attach(self.object, "", {z=5.62706,y=8.25,x=-4.0}, {x=0,y=0,z=-self.wheelpos*8})
self.object:setyaw(yaw - ((self.wheelpos/8)*(self.v/8)*dtime))
end
local sign
if self.v == 0 then sign = 0 else sign = get_sign(self.v) end
if sign ~= 0 then
self.v = self.v - 2*dtime*get_sign(self.v)
if get_sign(self.v) ~= sign then
self.v = 0
end
end
end
if attachTimer >= 5 then
for id, passengers in pairs (self.passengers) do
local player = passengers.player
if player then
player:set_attach(self.object, "",
passengers.loc, {x = 0, y = 0, z = 0})
end
end
end
if self.v > def.max_speed then
self.v = def.max_speed
elseif self.v < -1*def.max_speed/2 then
self.v = -1*def.max_speed/2
end
if math.abs(self.v) > 1 and minetest.get_item_group(minetest.get_node(pos).name, "water") > 0 then
self.v = 1*get_sign(self.v)
end
local new_velo
local velocity = self.object:getvelocity()
new_velo = get_velocity(self.v, self.object:getyaw(), velocity)
self.object:setvelocity(new_velo)
if math.abs(self.v) < .05 and math.abs(self.v) > 0 then
self.object:setvelocity({x = 0, y = 0, z = 0})
self.v = 0
if self.wheelsound then
minetest.sound_fade(self.wheelsound, 30, 0)
end
if self.windsound then
minetest.sound_fade(self.windsound, 30, 0)
end
wheelspeed(self)
return
end
if self.lastv and vector.length(self.lastv) == 0 and math.abs(self.v) > 0 then
wheelspeed(self)
end
--[[set acceleration for replication
if self.lastv then
local accel = vector.subtract(self.lastv, new_velo)
if self.v < 1 then
accel = {x=0,y=0,z=0}
end
accel = vector.multiply(accel, 20)
accel.y = -10
self.object:setacceleration(accel)
end--]]
self.lastv = new_velo
--sound
local abs_v = math.abs(self.v)
if abs_v > 0 and driver ~= nil then
self.timer1 = self.timer1 + dtime
if self.timer1 > .1 then
local rpm = 1
if abs_v > 16 then
rpm = abs_v/16+.5
elseif abs_v > 10 then
rpm = abs_v/10+.4
else
rpm = abs_v/5+.3
end
minetest.sound_play(def.enginesound, {
max_hear_distance = 48,
pitch = rpm+.1,
object = self.object
})
self.timer1 = 0
end
end
self.timer2 = self.timer2 + dtime
if self.timer2 > 1.5-self.v/def.max_speed*1.1 then
if abs_v > .2 then
if math.abs(velocity.y) < .1 then
self.wheelsound = minetest.sound_play("tyresound", {
max_hear_distance = 48,
object = self.object,
pitch = 1 + (abs_v/def.max_speed)*.6,
gain = .5 + (abs_v/def.max_speed)*2
})
elseif self.windsound then
minetest.sound_fade(self.windsound, 30, 0)
end
self.windsound = minetest.sound_play("wind", {
max_hear_distance = 10,
object = self.object,
pitch = 1 + (abs_v/def.max_speed)*.6,
gain = 0 + (abs_v/def.max_speed)*4
})
end
self.timer2 = 0
end
end
cars_registered_cars = {}
function cars_register_car(def)
cars_registered_cars[def.name] = def
minetest.register_entity(def.name, {
initial_properties = def.initial_properties,
on_activate = function(self, staticdata)
if not self.wheelpos then self.wheelpos = 0 end
if not self.timer1 then self.timer1 = 0 end
if not self.timer2 then self.timer2 = 0 end
if not self.platenumber then
self.platenumber = {}
end
self.passengers = def.passengers
if staticdata then
local deserialized = minetest.deserialize(staticdata)
if deserialized then
self.trunkinv = deserializeContents(deserialized.trunk)
if deserialized.plate then
self.platenumber.text = deserialized.plate.text
end
end
end
if not self.platenumber.text or self.platenumber.text == "" then self.platenumber.text = randomNumber(3).."-"..randomString(3) end
self.object:setacceleration({x=0, y=-10, z=0})
self.object:set_armor_groups({immortal = 1})
self.wheel = {}
wheelspeed(self)
local pos = self.object:getpos()
for index, wheel in pairs(def.wheel) do
if not self.wheel[index] then
self.wheel[index] = minetest.add_entity(pos, "cars:wheel")
end
if self.wheel[index] then
self.wheel[index]:set_attach(self.object, "", wheel, {x=0,y=0,z=0})
end
end
if not self.steeringwheel then
self.steeringwheel = minetest.add_entity(pos, "cars:steeringwheel")
end
if self.steeringwheel then
self.steeringwheel:set_attach(self.object, "", def.steeringwheel, {x=0,y=0,z=0})
end
--[[if not self.driverseat then
self.driverseat = minetest.add_entity(pos, "cars:seat")
end
if self.driverseat then
self.driverseat:set_attach(self.object, "", {x = -4, y = 3, z = 3}, {x = 0, y = 0, z = 0})
end--]]
if not self.licenseplate and minetest.get_modpath("signs") ~= nil then
self.licenseplate = minetest.add_entity(pos, "cars:licenseplate")
end
if self.licenseplate then
self.licenseplate:set_attach(self.object, "", def.licenseplate, {x = 0, y = 0, z = 0})
end
end,
get_staticdata = function(self)
return minetest.serialize({trunk = serializeContents(self.trunkinv), plate = self.platenumber})
end,
on_step = function(self, dtime)
car_step(self, dtime)
end,
on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir)
if puncher == self.passengers[1].player then
minetest.sound_play(def.horn, {
max_hear_distance = 48,
gain = 8,
object = self.object
})
return
end
if (puncher:get_wielded_item():get_name() == "") and (time_from_last_punch >= tool_capabilities.full_punch_interval) and math.random(1,2) == 1 then
local closeid = getClosest(puncher, self)
if DEBUG_TEXT then
minetest.chat_send_all(tostring(closeid))
end
if not closeid or closeid == 0 then return end
detach(self.passengers[closeid].player)
end
end,
on_rightclick = function(self, clicker)
if not clicker or not clicker:is_player() then
return
end
local name = clicker:get_player_name()
if player_attached[name] == self then
detach(clicker)
elseif player_attached[name] then
return
else
local i = 0
local closeid = getClosest(clicker, self)
if DEBUG_TEXT then
minetest.chat_send_all(tostring(closeid))
end
if closeid then
if closeid == 0 then
minetest.sound_play("opentrunk", {
max_hear_distance = 24,
gain = 1,
object = self.object
})
trunk_rightclick(self, clicker)
return
end
if not self.passengers[closeid].player then
i = closeid
end
else
while i <= #self.passengers do
i = i + 1
if not self.passengers[i].player then break end
end
end
if i == 0 or i == #self.passengers+1 then return end
self.passengers[i].player = clicker
--add hud for driver
if i == 1 then
self.hud = clicker:hud_add({
hud_elem_type = "text",
position = {x = 0.5, y = 0.8},
offset = {x = 0, y = 0},
text = tostring(math.abs(math.floor(self.v*2.23694*10)/10)).." MPH",
alignment = {x = 0, y = 0}, -- center aligned
scale = {x = 100, y = 100}, -- covered later
number = 0xFFFFFF,
})
end
player_attached[name] = self
clicker:set_attach(self.object, "",
self.passengers[i].loc, {x = 0, y = 0, z = 0})
clicker:set_eye_offset(self.passengers[i].offset, {x=0,y=0,z=0})
default.player_attached[name] = true
minetest.after(.1, function()
default.player_set_animation(clicker, "sit" , 30)
end)
clicker:set_look_horizontal(self.object:getyaw())
end
end
})
minetest.register_craftitem(def.name, {
description = def.description,
inventory_image = def.inventory_image,
on_place = function(itemstack, placer, pointed_thing)
if pointed_thing.type ~= "node" then
return
end
local ent
if minetest.get_item_group(minetest.get_node(pointed_thing.under).name, "liquid") == 0 then
pointed_thing.above.y = pointed_thing.above.y - 0.5
ent = minetest.add_entity(pointed_thing.above, def.name)
end
ent:setyaw(placer:get_look_yaw() - math.pi/2)
itemstack:take_item()
return itemstack
end
})
if def.recipe then
minetest.register_craft({
output = def.name,
recipe = def.recipe
})
end
end
minetest.register_entity("cars:wheel", {
hp_max = 1,
physical = false,
pointable = false,
collide_with_objects = false,
weight = 5,
collisionbox = {-0.2,-0.2,-0.2, 0.2,0.2,0.2},
visual = "mesh",
visual_size = {x=1, y=1},
mesh = "wheel.x",
textures = {"car_dark_grey.png"}, -- number of required textures depends on visual
is_visible = true,
--makes_footstep_sound = false,
--automatic_rotate = true,
on_activate = function(self, staticdata, dtime_s)
minetest.after(.1, function()
if not self.object:get_attach() then
self.object:remove()
end
end)
end,
})
minetest.register_entity("cars:licenseplate", {
collisionbox = { 0, 0, 0, 0, 0, 0 },
visual = "upright_sprite",
textures = {"invisible.png"},
visual_size = {x=1.2, y=1.2, z=1.2},
physical = false,
pointable = false,
collide_with_objects = false,
on_activate = function(self)
minetest.after(.1, function()
if not self.object:get_attach() or minetest.get_modpath("signs") == nil then
self.object:remove()
else
self.object:set_armor_groups({immortal = 1})
local text = self.object:get_attach():get_luaentity().platenumber.text
if not text then return end
self.object:set_properties({textures={generate_texture(create_lines(text))}})
end
end)
end
})
minetest.register_entity("cars:steeringwheel", {
hp_max = 1,
physical = false,
pointable = false,
collide_with_objects = false,
weight = 5,
collisionbox = {-0.2,-0.3,-0.2, 0.2,0.3,0.2},
visual = "mesh",
visual_size = {x=1, y=1},
mesh = "steering.x",
textures = {"car_dark_grey.png"}, -- number of required textures depends on visual
is_visible = true,
--makes_footstep_sound = false,
--automatic_rotate = true,
on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir)
minetest.sound_play("horn", {
max_hear_distance = 48,
gain = 8,
object = self.object
})
end,
on_activate = function(self, staticdata, dtime_s)
minetest.after(.1, function()
if not self.object:get_attach() then
self.object:remove()
else
self.object:set_armor_groups({immortal = 1})
end
end)
end,
})
minetest.register_on_leaveplayer(function(player)
detach(player)
end)
minetest.register_on_dieplayer(function(player)
detach(player)
end)
dofile(minetest.get_modpath("cars").."/car01.lua")