diff --git a/.gitignore b/.gitignore index a57dbc9..cba1856 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ *bak* tags *.vim +shooter.conf diff --git a/crossbow.lua b/crossbow.lua new file mode 100644 index 0000000..5867e68 --- /dev/null +++ b/crossbow.lua @@ -0,0 +1,254 @@ +SHOOTER_CROSSBOW_USES = 50 +SHOOTER_ARROW_TOOL_CAPS = {damage_groups={fleshy=2}} +SHOOTER_ARROW_LIFETIME = 180 -- 3 minutes + +local function get_animation_frame(dir) + local angle = math.atan(dir.y) + local frame = 90 - math.floor(angle * 360 / math.pi) + if frame < 1 then + frame = 1 + elseif frame > 180 then + frame = 180 + end + return frame +end + +local function get_target_pos(p1, p2, dir, offset) + local d = vector.distance(p1, p2) - offset + local td = vector.multiply(dir, {x=d, y=d, z=d}) + return vector.add(p1, td) +end + +local function punch_object(puncher, object) + if puncher and shooter:is_valid_object(object) then + if puncher ~= object then + local dir = puncher:get_look_dir() + local p1 = puncher:getpos() + local p2 = object:getpos() + local tpos = get_target_pos(p1, p2, dir, 0) + shooter:spawn_particles(tpos, SHOOTER_EXPLOSION_TEXTURE) + object:punch(puncher, nil, SHOOTER_ARROW_TOOL_CAPS, dir) + end + end +end + +local function stop_arrow(object, pos, stuck) + local acceleration = {x=0, y=-10, z=0} + if stuck == true then + pos = pos or object:getpos() + acceleration = {x=0, y=0, z=0} + object:moveto(pos) + end + object:set_properties({ + physical = true, + collisionbox = {-1/8,-1/8,-1/8, 1/8,1/8,1/8}, + }) + object:setvelocity({x=0, y=0, z=0}) + object:setacceleration(acceleration) +end + +minetest.register_craftitem("shooter:arrow", { + description = "Arrow", + inventory_image = "shooter_arrow_inv.png", +}) + +minetest.register_entity("shooter:arrow_entity", { + physical = false, + visual = "mesh", + mesh = "shooter_arrow.b3d", + visual_size = {x=1, y=1}, + textures = { + "shooter_arrow_uv.png", + }, + timer = 0, + lifetime = SHOOTER_ARROW_LIFETIME, + player = nil, + state = "init", + node_pos = nil, + collisionbox = {0,0,0, 0,0,0}, + on_activate = function(self, staticdata) + self.object:set_armor_groups({immortal=1}) + if staticdata == "expired" then + self.object:remove() + end + end, + on_punch = function(self, puncher) + if puncher then + if puncher:is_player() then + local stack = "shooter:arrow" + local inv = puncher:get_inventory() + if inv:room_for_item("main", stack) then + inv:add_item("main", stack) + self.object:remove() + end + end + end + end, + on_step = function(self, dtime) + if self.state == "init" then + return + end + self.timer = self.timer + dtime + self.lifetime = self.lifetime - dtime + if self.lifetime < 0 then + self.object:remove() + return + elseif self.state == "dropped" then + return + elseif self.state == "stuck" then + if self.timer > 1 then + if self.node_pos then + local node = minetest.get_node(self.node_pos) + if node.name then + local item = minetest.registered_items[node.name] + if item then + if not item.walkable then + self.state = "dropped" + stop_arrow(self.object) + return + end + end + end + end + self.timer = 0 + end + return + end + if self.timer > 0.2 then + local pos = self.object:getpos() + local dir = vector.normalize(self.object:getvelocity()) + local frame = get_animation_frame(dir) + self.object:set_animation({x=frame, y=frame}, 0) + local objects = minetest.get_objects_inside_radius(pos, 5) + for _,obj in ipairs(objects) do + if shooter:is_valid_object(obj) then + local collisionbox = {-0.25,-1.0,-0.25, 0.25,0.8,0.25} + local offset = SHOOTER_PLAYER_OFFSET + if not obj:is_player() then + offset = SHOOTER_ENTITY_OFFSET + local ent = obj:get_luaentity() + if ent then + local def = minetest.registered_entities[ent.name] + collisionbox = def.collisionbox or collisionbox + end + end + local opos = vector.add(obj:getpos(), offset) + local ray = {pos=pos, dir=dir} + local plane = {pos=opos, normal={x=-1, y=0, z=-1}} + local ipos = shooter:get_intersect_pos(ray, plane, collisionbox) + if ipos then + punch_object(self.player, obj) + end + end + end + local p = vector.add(pos, vector.multiply(dir, {x=5, y=5, z=5})) + local _, npos = minetest.line_of_sight(pos, p, 1) + if npos then + local node = minetest.get_node(npos) + local tpos = get_target_pos(pos, npos, dir, 0.66) + self.node_pos = npos + self.state = "stuck" + stop_arrow(self.object, tpos, true) + shooter:play_node_sound(node) + end + self.timer = 0 + end + end, + get_staticdata = function(self) + return "expired" + end, +}) + +minetest.register_tool("shooter:crossbow_loaded", { + description = "Crossbow", + inventory_image = "shooter_crossbow_loaded.png", + groups = {not_in_creative_inventory=1}, + on_use = function(itemstack, user, pointed_thing) + minetest.sound_play("shooter_click", {object=user}) + if not minetest.setting_getbool("creative_mode") then + itemstack:add_wear(65535/SHOOTER_CROSSBOW_USES) + end + itemstack = "shooter:crossbow 1 "..itemstack:get_wear() + local pos = user:getpos() + local dir = user:get_look_dir() + local yaw = user:get_look_yaw() + if pos and dir and yaw then + pos.y = pos.y + 1.5 + local obj = minetest.add_entity(pos, "shooter:arrow_entity") + local ent = nil + if obj then + ent = obj:get_luaentity() + end + if ent then + minetest.sound_play("shooter_throw", {object=obj}) + local frame = get_animation_frame(dir) + obj:setyaw(yaw + math.pi) + obj:set_animation({x=frame, y=frame}, 0) + obj:setvelocity({x=dir.x * 14, y=dir.y * 14, z=dir.z * 14}) + if pointed_thing.type ~= "nothing" then + local ppos = minetest.get_pointed_thing_position(pointed_thing, false) + local _, npos = minetest.line_of_sight(pos, ppos, 1) + if npos then + ppos = npos + pointed_thing.type = "node" + end + if pointed_thing.type == "object" then + punch_object(user, pointed_thing.ref) + elseif pointed_thing.type == "node" then + local node = minetest.get_node(ppos) + local tpos = get_target_pos(pos, ppos, dir, 0.66) + minetest.after(0.2, function(object, pos, npos) + ent.node_pos = npos + ent.state = "stuck" + stop_arrow(object, pos, true) + shooter:play_node_sound(node) + end, obj, tpos, ppos) + return itemstack + end + end + obj:setacceleration({x=dir.x * -3, y=-5, z=dir.z * -3}) + ent.player = ent.player or user + ent.state = "flight" + end + end + return itemstack + end, +}) + +minetest.register_tool("shooter:crossbow", { + description = "Crassbow", + inventory_image = "shooter_crossbow.png", + on_use = function(itemstack, user, pointed_thing) + local inv = user:get_inventory() + if inv:contains_item("main", "shooter:arrow") then + minetest.sound_play("shooter_reload", {object=user}) + if not minetest.setting_getbool("creative_mode") then + inv:remove_item("main", "shooter:arrow 1") + end + itemstack = "shooter:crossbow_loaded 1 "..itemstack:get_wear() + else + minetest.sound_play("shooter_click", {object=user}) + end + return itemstack + end, +}) + +if SHOOTER_ENABLE_CRAFTING == true then + minetest.register_craft({ + output = "shooter:crossbow", + recipe = { + {"default:stick", "default:stick", "default:stick"}, + {"default:stick", "default:stick", ""}, + {"default:stick", "", "default:bronze_ingot"}, + }, + }) + minetest.register_craft({ + output = "shooter:arrow", + recipe = { + {"default:steel_ingot", "", ""}, + {"", "default:stick", "default:paper"}, + {"", "default:paper", "default:stick"}, + }, + }) +end + diff --git a/init.lua b/init.lua index 4c0471b..1eceb96 100644 --- a/init.lua +++ b/init.lua @@ -2,6 +2,9 @@ local modpath = minetest.get_modpath(minetest.get_current_modname()) dofile(modpath.."/shooter.lua") +if SHOOTER_ENABLE_CROSSBOW == true then + dofile(modpath.."/crossbow.lua") +end if SHOOTER_ENABLE_GUNS == true then dofile(modpath.."/guns.lua") end diff --git a/models/shooter_arrow.b3d b/models/shooter_arrow.b3d new file mode 100644 index 0000000..d24be3c Binary files /dev/null and b/models/shooter_arrow.b3d differ diff --git a/models/shooter_arrow.blend b/models/shooter_arrow.blend new file mode 100644 index 0000000..4cf5f43 Binary files /dev/null and b/models/shooter_arrow.blend differ diff --git a/models/shooter_arrow_uv.png b/models/shooter_arrow_uv.png new file mode 100644 index 0000000..779d2ae Binary files /dev/null and b/models/shooter_arrow_uv.png differ diff --git a/shooter.conf.example b/shooter.conf.example index 3c2ce1b..54c6282 100644 --- a/shooter.conf.example +++ b/shooter.conf.example @@ -5,6 +5,9 @@ -- Enable node destruction with explosives SHOOTER_ENABLE_BLASTING = true +-- Enable Crossbow +SHOOTER_ENABLE_CROSSBOW = true + -- Enable basic guns (Pistol, Rifle, Shotgun, Machine Gun) SHOOTER_ENABLE_GUNS = true diff --git a/textures/shooter_arrow_inv.png b/textures/shooter_arrow_inv.png new file mode 100644 index 0000000..7727c5f Binary files /dev/null and b/textures/shooter_arrow_inv.png differ diff --git a/textures/shooter_arrow_uv.png b/textures/shooter_arrow_uv.png new file mode 100644 index 0000000..57017c8 Binary files /dev/null and b/textures/shooter_arrow_uv.png differ diff --git a/textures/shooter_crossbow.png b/textures/shooter_crossbow.png new file mode 100644 index 0000000..f832c26 Binary files /dev/null and b/textures/shooter_crossbow.png differ diff --git a/textures/shooter_crossbow_loaded.png b/textures/shooter_crossbow_loaded.png new file mode 100644 index 0000000..bd46531 Binary files /dev/null and b/textures/shooter_crossbow_loaded.png differ