--[[ Copyright (C) 2013, Diego Martínez All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ]] local HQ_FONT = true; local FONT_CHAR_W, FONT_CHAR_H; local FONT_AMMO_SCALE, FONT_CLIP_AMMO_SCALE; if (HQ_FONT) then FONT_CHAR_W = 13; FONT_CHAR_H = 16; FONT_CLIP_AMMO_SCALE = {x=2, y=2}; FONT_AMMO_SCALE = {x=1, y=1}; FONT_TEX_PREFIX = "hq_"; else FONT_CHAR_W = 3; FONT_CHAR_H = 5; FONT_CLIP_AMMO_SCALE = {x=8, y=8}; FONT_AMMO_SCALE = {x=4, y=4}; FONT_TEX_PREFIX = ""; end firearmslib.bullets = { }; firearmslib.firearms = { }; local function count_ammo ( gundef, player ) local inv = player:get_inventory(); local size = inv:get_size("main"); local bulletname = gundef.bullets; local count = 0; for i = 1, size do local stk = inv:get_stack("main", i); local nm = stk:get_name(); if (nm and (nm == bulletname)) then count = count + stk:get_count(); end end return count; end minetest.register_entity("firearmslib:smokepuff", { physical = false; timer = 0; textures = { "smoke_puff.png" }; collisionbox = { 0, 0, 0, 0, 0, 0 }; on_step = function ( self, dtime ) self.timer = self.timer + dtime; if (self.timer > 1) then self.object:remove(); end end; }); local wielded_firearm = { }; local function make_number_texture ( n ) local s = tostring(n); local xoff = FONT_CHAR_W + 1; local w = (s:len()*xoff); -- [combine:WxH:X,Y=filename:X,Y=filename2 local tex = "^[combine:"..(w - 1).."x"..FONT_CHAR_H; for i = 1, s:len() do local t = "firearms_"..FONT_TEX_PREFIX..s:sub(i, i)..".png"; tex = tex..":"..((i - 1) * xoff)..",0="..t; end return tex; end local function set_ammo ( player, clip, resv ) local wf = wielded_firearm[player:get_player_name()]; player:hud_change(wf.hud_clip_ammo, "text", make_number_texture(clip)); if (resv) then player:hud_change(wf.hud_ammo, "text", make_number_texture(resv)); end end local on_killentity_cbs = { }; function firearmslib.register_on_killentity ( func ) on_killentity_cbs[#on_killentity_cbs + 1] = func; end local function shoot ( itemstack, player, pointed_thing ) local gunname = itemstack:get_name(); local inv = player:get_inventory("main"); local gundef = firearmslib.firearms[gunname]; local bulletname = gundef.bullets; local bulletdef = firearmslib.bullets[bulletname]; local burst = gundef.burst or 1; local clip = tonumber(itemstack:get_metadata()) or 0; local function do_shoot ( param ) local pellets = bulletdef.pellets or 1; for n = 1, pellets do local spreadx = (-gundef.spread) + (math.random() * gundef.spread * 2); local spready = (-gundef.spread) + (math.random() * gundef.spread * 2); local spreadz = (-gundef.spread) + (math.random() * gundef.spread * 2); local pos = player:getpos(); pos.y = pos.y + 1.625; local dir = player:get_look_dir(); pos.x = pos.x + (dir.x / 2); pos.y = pos.y + (dir.y / 2); pos.z = pos.z + (dir.z / 2); if (bulletdef.speed) then -- Entity based bullet local bullet = minetest.env:add_entity( {x=pos.x, y=pos.y + 1.5, z=pos.z }, bulletname.."_entity" ); local ent = bullet:get_luaentity(); ent.bulletdef = bulletdef; ent.source = player; bullet:setvelocity({ x=((dir.x + spreadx) * bulletdef.speed), y=((dir.y + spready) * bulletdef.speed), z=((dir.z + spreadz) * bulletdef.speed), }); bullet:setacceleration({ x=0, y=-(bulletdef.gravity or 1), z=0 }); else -- Instant hit. dir.x = dir.x + spreadx; dir.y = dir.y + spready; dir.z = dir.z + spreadz; local obj = kutils.find_pointed_thing({ pos = pos; delta = dir; range = 20; radius = 2; user = player; }); --print("DEBUG: pointed object: "..dump(obj)); local vel = { x = dir.x * 8; y = dir.y * 8; z = dir.z * 8; }; -- Flying bullet (thanks to Exio for the idea) minetest.add_particle( pos, -- pos vel, -- velocity {x=0,y=0,z=0}, -- acceleration 0.2, -- expirationtime 0.3, -- size false, -- collisiondetection "default_wood.png"--, -- texture --nil -- playername ); if (obj) then if (firearmslib.ENABLE_BREAKING_GLASS and obj.node and firearmslib.BREAKING_GLASS_NODES[obj.node.name]) then if (minetest.get_modpath("item_drop")) then minetest.spawn_item(obj.pos, obj.node.name); end minetest.env:remove_node(obj.pos); elseif (obj.entity) then --local dist = kutils.distance3d(player:getpos(), obj.entity:getpos()); local ent = obj.entity; ent:set_hp(ent:get_hp() - bulletdef.power); if (ent:get_hp() <= 0) then if (not ent:is_player()) then ent:remove(); end for i,f in ipairs(firearmslib.on_killentity_cbs) do f(ent, player); end end end end end end local sound = (gundef.sounds and gundef.sounds.shoot); minetest.sound_play(sound or 'firearms_default_blast', { pos = playerpos; max_hear_distance = 20; }); local pos = player:getpos(); pos.y = pos.y + 1.5; local dir = player:get_look_dir(); pos.x = pos.x + (dir.x / 2); pos.y = pos.y + (dir.y / 2); pos.z = pos.z + (dir.z / 2); local vel = { x = (math.random(-15, 15) / 100), y = 0.1, z = (math.random(-15, 15) / 100), }; -- Spent cartridge (thanks to VanessaE for the idea) minetest.add_particle( pos, -- pos vel, -- velocity {x=0, y=-2, z=0}, -- acceleration 3, -- expirationtime 0.6, -- size true, -- collisiondetection bulletdef.inventory_image ); if (param and (param > 0)) then minetest.after(gundef.burst_interval, do_shoot, param - 1); end end if (player:get_player_control().sneak) then -- Reload. local ammo = count_ammo(gundef, player); local needed = gundef.clip_size - clip; needed = math.min(needed, ammo); if (needed == 0) then return; end --print(("DEBUG: Reloading: ammo=%d, needed=%d, clip=%d"):format(ammo, needed, clip)); inv:remove_item("main", bulletname.." "..needed); set_ammo(player, clip+needed, ammo-needed); if (gundef.sounds and gundef.sounds.reload) then minetest.sound_play(gundef.sounds.reload, { pos = playerpos; max_hear_distance = 50; }); end return ItemStack({name=gundef.name, metadata=tostring(clip+needed)}); end if (clip <= 0) then if (gundef.sounds.empty) then minetest.sound_play(gundef.sounds.empty, { pos = playerpos; max_hear_distance = 20; }); end return; end burst = math.min(burst, clip); clip = clip - burst; --local creative = minetest.setting_getbool("creative_mode"); if (creative) then do_shoot(burst - 1, bulletdef.speed); else do_shoot(burst - 1, bulletdef.speed); set_ammo(player, clip, nil); return ItemStack({name=gundef.name, metadata=tostring(clip)}); end end firearmslib.register_firearm = function ( name, def ) def.name = name; firearmslib.firearms[name] = def; minetest.register_tool(name, { description = def.description or "Unnamed Gun"; inventory_image = def.inventory_image or "firearms_unknown.png"; stack_max = 1; on_use = shoot; type = "tool"; wield_scale = def.wield_scale; }); end firearmslib.register_bullet = function ( name, def ) firearmslib.bullets[name] = def; minetest.register_craftitem(name, { description = def.description or "Unnamed Bullets"; inventory_image = def.inventory_image; stack_max = def.stack_max or 10; }); if (def.speed) then local ent = { physical = (def.physical or false); timer = 0; textures = { (def.texture or "firearms_bullet_entity.png") }; lastpos = { }; collisionbox = { 0, 0, 0, 0, 0, 0 }; def = def; _destroy = function ( self ) if (self.def.on_destroy) then self.def.on_destroy(self); end self.object:remove(); end; }; ent.on_step = function ( self, dtime ) self.timer = self.timer + dtime; local pos = self.object:getpos(); local node = minetest.env:get_node(pos); --[[if ((self.def.leaves_smoke) and (self.lastpos.x)) then local smoke = minetest.env:add_entity( self.lastpos, "firearms:smokepuff" ); end]] if (self.timer > 0.10) then local objs = minetest.env:get_objects_inside_radius({x=pos.x,y=pos.y,z=pos.z}, 1); local bulletname = self.object:get_entity_name():sub(1, -8); local damage = firearmslib.bullets[bulletname].damage; for k, obj in pairs(objs) do obj:set_hp(obj:get_hp() - damage); if ((obj:get_entity_name() ~= self.object:get_entity_name()) and (obj:get_entity_name() ~= "firearms:smokepuff")) then if (obj.entity:get_hp() <= 0) then if (not obj.entity:is_player()) then obj.entity:remove(); else for _,f in ipairs(on_killentity_cbs) do f(obj, player); end end end self:_destroy(); --local blood = minetest.env:add_entity({x=pos.x ,y=pos.y ,z=pos.z -0.5 }, "rifle:Blood_entity"); end end end if (self.timer >= (self.def.maxtimer or 3)) then self:_destroy(); return; end if (self.lastpos.x ~= nil) then if (node.name ~= "air") then self:_destroy(); return; end end self.lastpos = { x=pos.x, y=pos.y, z=pos.z }; end minetest.register_entity(name.."_entity", ent); end end firearmslib.on_destroy_explode = function ( self ) local explosion_range = self.def.explosion_range or 0; local explosion_damage = self.def.explosion_damage or 0; if (explosion_range <= 0) then minetest.debug("firearmslib: explosion has no range"); end if (explosion_damage <= 0) then minetest.debug("firearmslib: explosion has no damage"); end local p1 = self.object:getpos(); local ents = minetest.env:get_objects_inside_radius(p1, explosion_range); local sound = (self.def.sounds and self.def.sounds.explode) or "firearms_he_gren_explode"; minetest.sound_play(sound, { pos = self.object:getpos(); gain = 2.0; max_hear_distance = 150; }); firearmslib.explosion(self.object:getpos(), self.bulletdef); for _,ent in ipairs(ents) do local p2 = ent:getpos(); local lenx = math.abs(p2.x - p1.x); local leny = math.abs(p2.y - p1.y); local lenz = math.abs(p2.z - p1.z); local hypot = math.sqrt((lenx * lenx) + (lenz * lenz)); local dist = math.sqrt((hypot * hypot) + (leny * leny)); local damage = explosion_damage - (explosion_damage * dist / explosion_range); ent:set_hp(ent:get_hp() - damage); end end local timer = 0; local function remove_huds ( player, wf ) if (wf.hud_crosshair) then player:hud_remove(wf.hud_crosshair); wf.hud_crosshair = nil; end if (wf.hud_clip_ammo) then player:hud_remove(wf.hud_clip_ammo); wf.hud_clip_ammo = nil; end if (wf.hud_ammo) then player:hud_remove(wf.hud_ammo); wf.hud_ammo = nil; end end minetest.register_globalstep(function ( dtime ) timer = timer + dtime; if (timer < 0.5) then return; end timer = 0; for _,player in ipairs(minetest.get_connected_players()) do local name = player:get_player_name(); local stack = player:get_wielded_item(); local wpndef = firearmslib.firearms[stack:get_name()]; if (not wielded_firearm[name]) then wielded_firearm[name] = { }; end local wf = wielded_firearm[name]; if (wpndef) then if (wf.weapon ~= wpndef) then --minetest.chat_send_player(name, "New crosshair: "..wpndef.crosshair_image); wf.weapon = wpndef; if (wpndef.crosshair_image) then local clip = tonumber(stack:get_metadata()) or 0; local ammo = count_ammo(wpndef, player); remove_huds(player, wf); player:hud_set_flags({crosshair=false}); wf.hud_crosshair = player:hud_add({ name = "firearms:crosshair"; hud_elem_type = "image"; position = { x=0.5, y=0.5 }; text = wpndef.crosshair_image; scale = { x=1, y=1 }; alignment = { x=0, y=0 }; }); wf.hud_clip_ammo = player:hud_add({ name = "firearms:clip"; hud_elem_type = "image"; position = { x=1, y=1 }; text = make_number_texture(clip); scale = FONT_CLIP_AMMO_SCALE; alignment = { x=-1, y=-1 }; offset = { x = -8; y = -8 - (FONT_AMMO_SCALE.x * FONT_CHAR_H) - 8; }; }); wf.hud_ammo = player:hud_add({ name = "firearms:ammo"; hud_elem_type = "image"; position = { x=1, y=1 }; text = make_number_texture(ammo); scale = FONT_AMMO_SCALE; alignment = { x=-1, y=-1 }; offset = { x = -8; y = -8; }; }); else wpndef = nil; end end else wf.weapon = nil; end if (not wpndef) then player:hud_set_flags({crosshair=true}); remove_huds(player, wf); end end end); firearmslib.count_ammo = count_ammo; firearmslib.count_clip_ammo = count_clip_ammo; firearmslib.on_killentity_cbs = on_killentity_cbs;