commit 4ffab163030d92ddc00751e9e3bd2251a30d600b Author: Perttu Ahola Date: Sat Apr 1 18:17:00 2023 +0300 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..151e299 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.* +!.gitignore +*~ diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..f412f3d --- /dev/null +++ b/init.lua @@ -0,0 +1,443 @@ +-- Function to calculate the positions of nodes inside the box +-- box: A table containing yaw angle (in radians), dimensions, and the origin position +function get_nodes_in_box(box) + local yaw, dimensions, origin = box.yaw, box.dimensions, box.origin + local longitudinal, transverse, height = dimensions[1], dimensions[2], dimensions[3] + local nodes = {} + + local sin_yaw = math.sin(yaw) + local cos_yaw = math.cos(yaw) + + for x = -math.floor(longitudinal / 2), math.floor(longitudinal / 2) do + for y = 0, height - 1 do + for z = -math.floor(transverse / 2), math.floor(transverse / 2) do + local rotated_x = origin.x + (x * cos_yaw - z * sin_yaw) + local rotated_y = origin.y + y + local rotated_z = origin.z + (x * sin_yaw + z * cos_yaw) + + local node_pos = {x = math.floor(rotated_x), y = math.floor(rotated_y), z = math.floor(rotated_z)} + table.insert(nodes, node_pos) + end + end + end + + return nodes +end + +-- Function to get non-walkable nodes in the box +-- box: A table containing yaw angle (in radians), dimensions, and the origin position +function get_non_walkable_nodes_in_box(box) + local all_nodes = get_nodes_in_box(box) + local non_walkable_nodes = {} + + for _, node_pos in ipairs(all_nodes) do + local node = minetest.get_node(node_pos) + local node_def = minetest.registered_nodes[node.name] + if node_def and not node_def.walkable or node_def.drawtype ~= "normal" then + table.insert(non_walkable_nodes, node_pos) + end + end + + return non_walkable_nodes +end + +function get_walkable_nodes_in_box(box) + local all_nodes = get_nodes_in_box(box) + local walkable_nodes = {} + + for _, node_pos in ipairs(all_nodes) do + local node = minetest.get_node(node_pos) + local node_def = minetest.registered_nodes[node.name] + if node_def and node_def.walkable then + table.insert(walkable_nodes, node_pos) + end + end + + return walkable_nodes +end + +-- Function to create a virtual wall in front of the player and move nodes in front of the wall +function create_virtual_wall(player_pos_float, player_yaw, wall_dimensions, move_distance, + placement_height, placement_depth) + local player_pos = { + x = player_pos_float.x + 0.5, + y = player_pos_float.y + 0.5, + z = player_pos_float.z + 0.5, + } + + local wall_pos = { + x = player_pos.x + math.cos(player_yaw) * 0, + y = player_pos.y, + z = player_pos.z + math.sin(player_yaw) * 0, + } + + local wall_box = { + yaw = player_yaw, + dimensions = wall_dimensions, + origin = { + x = wall_pos.x + math.cos(player_yaw), + y = wall_pos.y + 1.5 - 1, + z = wall_pos.z + math.sin(player_yaw), + }, + } + + local nodes_in_wall = get_nodes_in_box(wall_box) + + -- Main placement box in front + + local placement_box = { + yaw = player_yaw, + dimensions = {move_distance, wall_dimensions[2], placement_height - placement_depth}, + origin = { + x = player_pos.x + math.cos(player_yaw) * 5, + y = player_pos.y + 1.5 + placement_depth, + z = player_pos.z + math.sin(player_yaw) * 5, + }, + } + + local non_walkable_nodes_in_placement_box = get_non_walkable_nodes_in_box(placement_box) + + table.sort(non_walkable_nodes_in_placement_box, function(a, b) return a.y < b.y end) + + -- Side placement boxes + + local side_box_width = wall_dimensions[2] + + local left_box = { + yaw = player_yaw, + dimensions = {move_distance, side_box_width, wall_dimensions[3] + 2}, + origin = { + x = player_pos.x + math.cos(player_yaw+math.pi/2) * side_box_width, + y = player_pos.y + 0.5 * wall_dimensions[3] - 3, + z = player_pos.z + math.sin(player_yaw+math.pi/2) * side_box_width, + }, + } + + local nwn_in_left_box = get_non_walkable_nodes_in_box(left_box) + + local right_box = { + yaw = player_yaw, + dimensions = {move_distance, side_box_width, wall_dimensions[3] + 2}, + origin = { + x = player_pos.x + math.cos(player_yaw-math.pi/2) * side_box_width, + y = player_pos.y + 0.5 * wall_dimensions[3] - 3, + z = player_pos.z + math.sin(player_yaw-math.pi/2) * side_box_width, + }, + } + + local nwn_in_right_box = get_non_walkable_nodes_in_box(right_box) + + local nwn_left_right_combined = {} + + for i, node_pos in ipairs(nwn_in_left_box) do + table.insert(nwn_left_right_combined, node_pos) + end + for i, node_pos in ipairs(nwn_in_right_box) do + table.insert(nwn_left_right_combined, node_pos) + end + + table.sort(nwn_left_right_combined, function(a, b) return a.y < b.y end) + + for i, node_pos in ipairs(nwn_left_right_combined) do + table.insert(non_walkable_nodes_in_placement_box, node_pos) + end + + local num_sounds_played = 0 + + for i, node_pos in ipairs(nodes_in_wall) do + local node = minetest.get_node(node_pos) + if node.name ~= "air" then + if #non_walkable_nodes_in_placement_box > 0 then + minetest.set_node(non_walkable_nodes_in_placement_box[1], node) + minetest.remove_node(node_pos) + table.remove(non_walkable_nodes_in_placement_box, 1) + + if num_sounds_played < 1 then + local node_def = minetest.registered_nodes[node.name] + if node_def then + local sounds = node_def.sounds + if sounds and sounds.dig then + num_sounds_played = num_sounds_played + 1 + minetest.sound_play(sounds.dig, {pos = pos, gain = 0.5}) + end + end + end + end + end + end +end + +function target_value(current, target, rate) + if current < target - rate then + return current + rate + elseif current > target + rate then + return current - rate + else + return target + end +end + +local BULLDOZER_SIZE = 3 +local BULLDOZER_HEIGHT = 1 +local CLEAR_HEIGHT = 10 +local PLACEMENT_HEIGHT = 3 +local PLACEMENT_DEPTH = -5 + +-- Register the bulldozer entity +minetest.register_entity("bulldozer:bulldozer", { + initial_properties = { + physical = true, + collisionbox = {-1.4, -0.5, -1.4, 1.4, 0.8, 1.4}, + visual = "mesh", + mesh = "bulldozer_bulldozer.obj", + textures = { + "bulldozer_bulldozer_blade.png", + "bulldozer_bulldozer_track.png", + "bulldozer_bulldozer_track.png", + "bulldozer_bulldozer_body.png", + "bulldozer_bulldozer_body.png", + "bulldozer_bulldozer_body.png", + }, + }, + driver = nil, + wanted_sound_pitch = 0.7, + played_sound_pitch = 0.0, + wanted_sound_gain = 0.2, + played_sound_gain = 0.0, + + on_rightclick = function(self, clicker) + if not clicker or not clicker:is_player() then + return + end + + local player_name = clicker:get_player_name() + + if self.driver and player_name == self.driver:get_player_name() then + -- Detach the player + self.driver:set_detach() + self.driver:set_eye_offset() + self.driver = nil + self.object:set_properties({ + physical = true, + }) + if self.sound_handle then minetest.sound_stop(self.sound_handle) end + elseif not self.driver then + -- Attach the player + self.driver = clicker + self.driver:set_attach(self.object, "", {x = 0, y = 0, z = 0}, {x = 0, y = -90, z = 0}) + self.driver:set_eye_offset({x = 0, y = 2, z = 0}) + self.object:set_properties({ + physical = false, -- We want to go through nodes + }) + self.wanted_sound_pitch = 0.7 + self.wanted_sound_gain = 0.2 + end + end, + + on_step = function(self, dtime) + if not self.driver then + self:update_sound() + return + end + + -- Get player control inputs + local ctrl = self.driver:get_player_control() + --local yaw = self.driver:get_look_horizontal() + local yaw = self.object:get_yaw() + + local object_pos = self.object:get_pos() + + local y_off = -0.5 + + local box = { + yaw = yaw, + dimensions = {(BULLDOZER_SIZE+1), (BULLDOZER_SIZE+1), 1}, + origin = { + x = object_pos.x, + y = object_pos.y - y_off + 0.9, + z = object_pos.z, + }, + } + local nwn_tracks = get_walkable_nodes_in_box(box) + + local box = { + yaw = yaw, + dimensions = {(BULLDOZER_SIZE+1), (BULLDOZER_SIZE+1), 1}, + origin = { + x = object_pos.x, + y = object_pos.y - y_off - 0.5, + z = object_pos.z, + }, + } + local nwn_support = get_walkable_nodes_in_box(box) + + local box = { + yaw = yaw, + dimensions = {(BULLDOZER_SIZE+1), (BULLDOZER_SIZE+1), 1}, + origin = { + x = object_pos.x, + y = object_pos.y - y_off - 0.15, + z = object_pos.z, + }, + } + local nwn_close_support = get_walkable_nodes_in_box(box) + + local box = { + yaw = yaw, + dimensions = {(BULLDOZER_SIZE+1), (BULLDOZER_SIZE+1), 1}, + origin = { + x = object_pos.x, + y = object_pos.y - y_off - 0.05, + z = object_pos.z, + }, + } + local nwn_very_close_support = get_walkable_nodes_in_box(box) + + if ctrl.up then + local speed = 2.0 + + -- Move the bulldozer forward + self.object:set_velocity(vector.new( + math.cos(yaw+math.pi) * speed, + 0, + math.sin(yaw+math.pi) * speed + )) + + local object_pos2 = self.object:get_pos() + if ctrl.jump then + object_pos2.y = object_pos2.y - y_off + 1.1 + elseif ctrl.sneak then + object_pos2.y = object_pos2.y - y_off - 0.9 + else + object_pos2.y = object_pos2.y - y_off - 0.5 + end + local object_yaw = self.object:get_yaw()+math.pi + + local wall_dimensions = {2, (BULLDOZER_SIZE+1), CLEAR_HEIGHT} + local move_distance = 5 + create_virtual_wall(object_pos2, object_yaw, wall_dimensions, move_distance, PLACEMENT_HEIGHT, PLACEMENT_DEPTH) + elseif ctrl.down then + local speed = 1.5 + -- Move the bulldozer backward + self.object:set_velocity(vector.new( + math.cos(yaw) * speed, + 0, + math.sin(yaw) * speed + )) + else + self.object:set_velocity({x = 0, y = 0, z = 0}) + end + + if ctrl.left then + self.object:set_yaw(self.object:get_yaw() + 0.020) + elseif ctrl.right then + self.object:set_yaw(self.object:get_yaw() - 0.020) + end + + if (ctrl.jump and ctrl.up and (#nwn_tracks >= 1 or #nwn_very_close_support >= (BULLDOZER_SIZE*BULLDOZER_SIZE/2))) or + (ctrl.down and #nwn_tracks >= 1 and not ctrl.sneak) then + local rate = 0.01 + if #nwn_very_close_support >= 9 then + rate = 0.03 + elseif #nwn_very_close_support >= 5 then + rate = 0.02 + end + self.object:set_pos({ + x = self.object:get_pos().x, + y = self.object:get_pos().y + rate, + z = self.object:get_pos().z + }) + elseif (ctrl.sneak and ctrl.up) then + local rate = 0.01 + if #nwn_support <= 7 then + rate = 0.02 + end + self.object:set_pos({ + x = self.object:get_pos().x, + y = self.object:get_pos().y - rate, + z = self.object:get_pos().z + }) + else + if #nwn_close_support >= (BULLDOZER_SIZE*BULLDOZER_SIZE*0.8) and not ctrl.sneak then + local off = -y_off + local new_y = math.floor(self.object:get_pos().y + off + 0.5) - off + if math.abs(new_y - self.object:get_pos().y) >= 0.2 then + self.object:set_pos({ + x = self.object:get_pos().x, + y = new_y, + z = self.object:get_pos().z + }) + end + end + end + + if not ctrl.sneak then + if #nwn_support <= (BULLDOZER_SIZE*BULLDOZER_SIZE/3) then + self.object:set_pos({ + x = self.object:get_pos().x, + --y = math.floor(self.object:get_pos().y + 0.5 - 1.0), + y = self.object:get_pos().y - 0.1, + z = self.object:get_pos().z + }) + elseif #nwn_close_support <= (BULLDOZER_SIZE*BULLDOZER_SIZE/3) then + self.object:set_pos({ + x = self.object:get_pos().x, + --y = math.floor(self.object:get_pos().y + 0.5 - 1.0), + y = self.object:get_pos().y - 0.02, + z = self.object:get_pos().z + }) + end + end + + if ctrl.up then + self.wanted_sound_pitch = target_value(self.wanted_sound_pitch, 1.3, 0.02) + self.wanted_sound_gain = target_value(self.wanted_sound_gain, 0.30, 0.02) + elseif ctrl.down or ctrl.left or ctrl.right then + self.wanted_sound_pitch = target_value(self.wanted_sound_pitch, 1.1, 0.02) + self.wanted_sound_gain = target_value(self.wanted_sound_gain, 0.25, 0.02) + else + self.wanted_sound_pitch = target_value(self.wanted_sound_pitch, 0.9, 0.02) + self.wanted_sound_gain = target_value(self.wanted_sound_gain, 0.20, 0.02) + end + self:update_sound() + end, + + update_sound = function(self) + if not self.driver then + if self.sound_handle then minetest.sound_stop(self.sound_handle) end + return + end + if math.abs(self.wanted_sound_pitch - self.played_sound_pitch) < 0.06 and + math.abs(self.wanted_sound_gain - self.played_sound_gain) < 0.03 then + return + end + if self.sound_handle then minetest.sound_stop(self.sound_handle) end + if self.object then + self.played_sound_pitch = self.wanted_sound_pitch + self.played_sound_gain = self.wanted_sound_gain + self.sound_handle = minetest.sound_play({name = "bulldozer_engine"}, { + object = self.object, gain = self.wanted_sound_gain, + pitch = self.wanted_sound_pitch, + max_hear_distance = 45, + loop = true, + }) + end + end, +}) + +-- Register the bulldozer item for spawning the entity +minetest.register_craftitem("bulldozer:bulldozer_item", { +description = "Bulldozer", +inventory_image = "bulldozer_bulldozer_item.png", +on_place = function(itemstack, placer, pointed_thing) +if pointed_thing.type ~= "node" then +return +end + local ent = minetest.add_entity(pointed_thing.above, "bulldozer:bulldozer") + ent:set_yaw(placer:get_look_horizontal()) + + itemstack:take_item() + return itemstack +end, +}) + diff --git a/media/bulldozer_bulldozer.obj b/media/bulldozer_bulldozer.obj new file mode 100644 index 0000000..ec761e7 --- /dev/null +++ b/media/bulldozer_bulldozer.obj @@ -0,0 +1,141 @@ +# Axis-aligned boxes for Minetest + +mtllib boxes.mtl + +# Blade +o Box1 +g Group1 +usemtl Texture1 +v 18.5 0 -15.0 +v 20.5 0 -15.0 +v 20.5 0 15.0 +v 18.5 0 15.0 +v 18.5 8 -15.0 +v 20.5 8 -15.0 +v 20.5 8 15.0 +v 18.5 8 15.0 +vt 0 0 +vt 1 0 +vt 1 1 +vt 0 1 +f 1/1 5/4 6/3 2/2 +f 2/1 6/4 7/3 3/2 +f 3/1 7/4 8/3 4/2 +f 4/1 8/4 5/3 1/2 +f 1/1 2/2 3/3 4/4 +f 5/1 8/2 7/3 6/4 + +# Left track +o Box2 +g Group2 +usemtl Texture2 +v -10.0 0 -14 +v 17.0 0 -14 +v 17.0 0 -8 +v -10.0 0 -8 +v -10.0 6 -14 +v 17.0 6 -14 +v 17.0 6 -8 +v -10.0 6 -8 +vt 0 0 +vt 1 0 +vt 1 1 +vt 0 1 +f 9/1 13/4 14/3 10/2 +f 10/1 14/4 15/3 11/2 +f 11/1 15/4 16/3 12/2 +f 12/1 16/4 13/3 9/2 +f 9/1 10/2 11/3 12/4 +f 13/1 16/2 15/3 14/4 + +# Right track +o Box3 +g Group3 +usemtl Texture3 +v -10.0 0 8 +v 17.0 0 8 +v 17.0 0 14 +v -10.0 0 14 +v -10.0 6 8 +v 17.0 6 8 +v 17.0 6 14 +v -10.0 6 14 +vt 0 0 +vt 1 0 +vt 1 1 +vt 0 1 +f 17/1 21/4 22/3 18/2 +f 18/1 22/4 23/3 19/2 +f 19/1 23/4 24/3 20/2 +f 20/1 24/4 21/3 17/2 +f 17/1 18/2 19/3 20/4 +f 21/1 24/2 23/3 22/4 + +# Body +o Box4 +g Group4 +usemtl Texture4 +v -10.0 1 -7.0 +v 17.0 1 -7.0 +v 17.0 1 7.0 +v -10.0 1 7.0 +v -10.0 10 -7.0 +v 17.0 10 -7.0 +v 17.0 10 7.0 +v -10.0 10 7.0 +vt 0 0 +vt 1 0 +vt 1 1 +vt 0 1 +f 25/1 29/4 30/3 26/2 +f 26/1 30/4 31/3 27/2 +f 27/1 31/4 32/3 28/2 +f 28/1 32/4 29/3 25/2 +f 25/1 26/2 27/3 28/4 +f 29/1 32/2 31/3 30/4 + +# Left track cover +o Box5 +g Group5 +usemtl Texture5 +v -9.0 1 -15 +v 16.0 1 -15 +v 16.0 1 -7 +v -9.0 1 -7 +v -9.0 5 -15 +v 16.0 5 -15 +v 16.0 5 -7 +v -9.0 5 -7 +vt 0 0 +vt 1 0 +vt 1 1 +vt 0 1 +f 33/1 37/4 38/3 34/2 +f 34/1 38/4 39/3 35/2 +f 35/1 39/4 40/3 36/2 +f 36/1 40/4 37/3 33/2 +f 33/1 34/2 35/3 36/4 +f 37/1 40/2 39/3 38/4 + +# Right track cover +o Box6 +g Group6 +usemtl Texture6 +v -9.0 1 7 +v 16.0 1 7 +v 16.0 1 15 +v -9.0 1 15 +v -9.0 5 7 +v 16.0 5 7 +v 16.0 5 15 +v -9.0 5 15 +vt 0 0 +vt 1 0 +vt 1 1 +vt 0 1 +f 41/1 45/4 46/3 42/2 +f 42/1 46/4 47/3 43/2 +f 43/1 47/4 48/3 44/2 +f 44/1 48/4 45/3 41/2 +f 41/1 42/2 43/3 44/4 +f 45/1 48/2 47/3 46/4 diff --git a/media/bulldozer_bulldozer_blade.png b/media/bulldozer_bulldozer_blade.png new file mode 100644 index 0000000..50d3cce Binary files /dev/null and b/media/bulldozer_bulldozer_blade.png differ diff --git a/media/bulldozer_bulldozer_body.png b/media/bulldozer_bulldozer_body.png new file mode 100644 index 0000000..fa8d0a1 Binary files /dev/null and b/media/bulldozer_bulldozer_body.png differ diff --git a/media/bulldozer_bulldozer_track.png b/media/bulldozer_bulldozer_track.png new file mode 100644 index 0000000..79e3dab Binary files /dev/null and b/media/bulldozer_bulldozer_track.png differ diff --git a/media/bulldozer_engine.ogg b/media/bulldozer_engine.ogg new file mode 100644 index 0000000..6260623 Binary files /dev/null and b/media/bulldozer_engine.ogg differ