local go = false local DEBUG_WAYPOINT = false local DEBUG_TEXT = false local max_speed = 20 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 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 = car.trunkloc or {x = 0, y = 4, z = -8} 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) 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 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 name = clicker:get_player_name() trunkplayer[name] = self local inventory = minetest.create_detached_inventory("cars_"..name, { 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, }) inventory:set_size("trunk", 12) local templist = table.copy(self.trunkinv) inventory:set_list("trunk", templist) local formspec = "size[8,8]".. "list[detached:cars_"..name..";trunk;1,1;6,2;]".. "list[current_player;main;0,4;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) 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 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 + 4*dtime else self.v = self.v + 10*dtime end elseif ctrl.down then if sign <= 0 then self.v = self.v - 4*dtime else self.v = self.v - 10*dtime end elseif sign ~= 0 then self.v = self.v - 2*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 > max_speed then self.v = max_speed elseif self.v < -1*max_speed/2 then self.v = -1*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 --if driver:get_player_control().up 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("longerenginefaded", { max_hear_distance = 48, pitch = rpm+.1, object = self.object }) --[[else minetest.sound_play("longerenginefaded", { max_hear_distance = 48, object = self.object }) --end--]] self.timer1 = 0 end end self.timer2 = self.timer2 + dtime if self.timer2 > 1.5-self.v/max_speed*1.1 then if math.abs(self.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 + (self.v/max_speed)*.6, gain = .5 + (self.v/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 + (self.v/max_speed)*.6, gain = 0 + (self.v/max_speed)*4 }) end self.timer2 = 0 end end local carlist = {"black", "blue", "brown", "cyan", "dark_green", "dark_grey", "green", "grey", "magenta", "orange", "pink", "red", "violet", "white", "yellow"} for id, color in pairs (carlist) do minetest.register_entity("cars:car_"..color, { hp_max = 1, physical = true, stepheight = 1.1, weight = 5, collisionbox = {-0.6, -0.05, -0.6, 0.6, 1.1, 0.6}, visual = "mesh", visual_size = {x=1, y=1}, mesh = "car.x", textures = {"car_"..color..".png^licenseplate.png"}, -- number of required textures depends on visual is_visible = true, makes_footstep_sound = false, automatic_rotate = false, trunkinv = {}, 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 = { {loc = {x = -4, y = 3, z = 3}, offset = {x = -4, y = -2, z = 2} }, {loc = {x = 4, y = 3, z = 3}, offset = {x = 4, y = -2, z = 2} }, {loc = {x = -4, y = 3, z = -4}, offset = {x = -4, y = -2, z = -2} }, {loc = {x = 4, y = 3, z = -4}, offset = {x = 4, y = -2, z = -2} }, } 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() if not self.wheel.frontright then self.wheel.frontright = minetest.add_entity(pos, "cars:wheel") end if self.wheel.frontright then self.wheel.frontright:set_attach(self.object, "", {z=10.75,y=2.5,x=-8.875}, {x=0,y=0,z=0}) end if not self.wheel.frontleft then self.wheel.frontleft = minetest.add_entity(pos, "cars:wheel") end if self.wheel.frontleft then self.wheel.frontleft:set_attach(self.object, "", {z=10.75,y=2.5,x=8.875}, {x=0,y=0,z=0}) end if not self.wheel.backright then self.wheel.backright = minetest.add_entity(pos, "cars:wheel") end if self.wheel.backright then self.wheel.backright:set_attach(self.object, "", {z=-11.75,y=2.5,x=-8.875}, {x=0,y=0,z=0}) end if not self.wheel.backleft then self.wheel.backleft = minetest.add_entity(pos, "cars:wheel") end if self.wheel.backleft then self.wheel.backleft:set_attach(self.object, "", {z=-11.75,y=2.5,x=8.875}, {x=0,y=0,z=0}) 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, "", {z=5.62706,y=8.25,x=-4.0}, {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 then self.licenseplate = minetest.add_entity(pos, "cars:licenseplate") end if self.licenseplate then self.licenseplate:set_attach(self.object, "", {x = -.38, y = -0.85, z = -15.51}, {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("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("cars:car_"..color, { description = color:gsub("^%l", string.upper):gsub("_", " ").." car", inventory_image = "inv_car_"..color..".png", 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, "cars:car_"..color) end ent:setyaw(placer:get_look_yaw() - math.pi/2) itemstack:take_item() return itemstack end }) --minetest.register_alias("cars:car_"..color, "vehicle_mash:car_"..color) minetest.register_craft({ output = "cars:car_"..color, recipe = { {"default:steel_ingot", "wool:"..color, "default:steel_ingot"}, {"default:steel_ingot", "default:mese_crystal", "default:steel_ingot"} } }) 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() 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_entity("cars:trunk", { on_activate = function(self, staticdata, dtime_s) self.object:remove() end }) minetest.register_on_leaveplayer(function(player) detach(player) end) minetest.register_on_dieplayer(function(player) detach(player) end)