diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..e103385 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Exclusions for release export +.* export-ignore diff --git a/.github/workflows/luacheck.yml b/.github/workflows/luacheck.yml index a13efa9..3c99a99 100644 --- a/.github/workflows/luacheck.yml +++ b/.github/workflows/luacheck.yml @@ -1,13 +1,10 @@ name: luacheck on: [push, pull_request] jobs: - build: + luacheck: runs-on: ubuntu-latest steps: - - uses: actions/checkout@master - - name: apt - run: sudo apt-get install -y luarocks - - name: luacheck install - run: luarocks install --local luacheck - - name: luacheck run - run: $HOME/.luarocks/bin/luacheck ./ + - name: Checkout + uses: actions/checkout@master + - name: Luacheck + uses: lunarmodules/luacheck@master diff --git a/.luacheckrc b/.luacheckrc index 2b9fc63..1502aa3 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -1,13 +1,11 @@ globals = { - "hangglider", - "areas" + "areas", } read_globals = { - -- Minetest "minetest", "vector", "ItemStack", - -- Mod deps - "player_monoids" + "player_monoids", + "unifieddyes", } diff --git a/README.md b/README.md new file mode 100644 index 0000000..d109a22 --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# Hang Glider [hangglider] + +[![luacheck](https://github.com/mt-mods/hangglider/workflows/luacheck/badge.svg)](https://github.com/mt-mods/hangglider/actions) +[![ContentDB](https://content.minetest.net/packages/mt-mods/hangglider/shields/downloads/)](https://content.minetest.net/packages/mt-mods/hangglider/) + +Adds a functional hang glider for exploring. Also works as a parachute to save yourself when falling. + +Rewritten and improved fork of https://notabug.org/Piezo_/minetest-hangglider. + +![](screenshot.png?raw=true) + +## Usage + +To deploy the hang glider, hold the item in your hand and use it (left-click). The same action also closes the hang glider when it's deployed. + +While deployed you can glide around just like walking in the air. Your decent will be slowed until you land on a safe node, which can be any solid node, or a safe liquid like water. Upon landing the hang glider will automatically close. + +## Coloring + +You can color the hang glider by crafting it with any dye. Also supports all `unifieddyes` colors. + +Note that the color will only be visible on the item if you are using Minetest 5.8.0 or above. + + +## Repairing + +The hang glider will wear out every time you use it. The hang glider can be repaired by crafting it with wool or paper, or any other method used to repair tools. + +## Area Flak + +If the `areas` mod is installed, airspace restrictions can be added to areas using the `/area_flak` command. + +When using a hang glider in an area with flak enabled, you will get shot down a few seconds after entering the area, this reduces your HP to 1 and destroys your hang glider. diff --git a/crafts.lua b/crafts.lua new file mode 100644 index 0000000..261ac17 --- /dev/null +++ b/crafts.lua @@ -0,0 +1,106 @@ + +local has_unifieddyes = minetest.get_modpath("unifieddyes") + +local dye_colors = { + white = "ffffff", + grey = "888888", + dark_grey = "444444", + black = "111111", + violet = "8000ff", + blue = "0000ff", + cyan = "00ffff", + dark_green = "005900", + green = "00ff00", + yellow = "ffff00", + brown = "592c00", + orange = "ff7f00", + red = "ff0000", + magenta = "ff00ff", + pink = "ff7f9f", +} + +local function get_dye_color(name) + local color + if has_unifieddyes then + color = unifieddyes.get_color_from_dye_name(name) + end + if not color then + color = string.match(name, "^dye:(%w+)$") + if color then + color = dye_colors[color] + end + end + return color +end + +local function get_color_name(name) + name = string.gsub(name, "^dye:", "") + name = string.gsub(name, "_", " ") + name = string.gsub(name, "(%l)(%w*)", function(a, b) return string.upper(a)..b end) + return name +end + +-- This recipe is just a placeholder +do + local item = ItemStack("hangglider:hangglider") + item:get_meta():set_string("description", "Colored Glider") + minetest.register_craft({ + output = item:to_string(), + recipe = {"hangglider:hangglider", "group:dye"}, + type = "shapeless", + }) +end + +-- This is what actually creates the colored hangglider +minetest.register_on_craft(function(crafted_item, _, old_craft_grid) + if crafted_item:get_name() ~= "hangglider:hangglider" then + return + end + local wear, color, color_name + for _,stack in ipairs(old_craft_grid) do + local name = stack:get_name() + if name == "hangglider:hangglider" then + wear = stack:get_wear() + elseif minetest.get_item_group(name, "dye") ~= 0 then + color = get_dye_color(name) + color_name = get_color_name(name) + end + end + if wear and color and color_name then + if color == "ffffff" then + return ItemStack({name = "hangglider:hangglider", wear = wear}) + end + local meta = crafted_item:get_meta() + meta:set_string("description", color_name.." Glider") + meta:set_string("inventory_image", "hangglider_item.png^(hangglider_color.png^[multiply:#"..color..")") + meta:set_string("hangglider_color", color) + crafted_item:set_wear(wear) + return crafted_item + end +end) + +-- Repairing +minetest.register_craft({ + output = "hangglider:hangglider", + recipe = { + {"default:paper", "default:paper", "default:paper"}, + {"default:paper", "hangglider:hangglider", "default:paper"}, + {"default:paper", "default:paper", "default:paper"}, + }, +}) +minetest.register_craft({ + output = "hangglider:hangglider", + recipe = { + {"hangglider:hangglider", "wool:white"}, + }, +}) + +-- Main craft +minetest.register_craft({ + output = "hangglider:hangglider", + recipe = { + {"wool:white", "wool:white", "wool:white"}, + {"default:stick", "", "default:stick"}, + {"", "default:stick", ""}, + } +}) diff --git a/init.lua b/init.lua index 84bab35..3953e5d 100644 --- a/init.lua +++ b/init.lua @@ -1,76 +1,20 @@ --- Hangglider mod for Minetest --- Original code by Piezo_ (orderofthefourthwall@gmail.com) --- 2018-11-14 --- Modifications by David G (kestral246@gmail.com) --- 2018-11-24 --- For Minetest 5.x, glider's set_attach needs to be offset by 1 node --- Switch to alternate commented line below with correct offset. --- Additional tuning of parameters. --- Commented out debug hud display code, prefixed with "--debug:". +local has_player_monoids = minetest.get_modpath("player_monoids") +local has_areas = minetest.get_modpath("areas") --- 2018-11-22 --- Give visual indication that hangglider is equiped. --- Display simple overlay with blurred struts when equiped. --- Issue: don't know how to disable overlay in third person view. --- Also Unequip hangglider when landing on water. --- Attempt to linearize parabolic flight path. --- Start gravity stronger, but gradually reduce it as descent velocity increases. --- Don't use airstopper when equipped from the ground (descent velocity is low). --- Slightly increase flight speed to 1.25. --- Unequip/equip cycling mid-flight should not fly farther than continuous flight. --- When equipping mid-air (descent velocity higher), use airstopper but increase descent slope afterwards. --- Create airbreak flag so all equips mid-flight use faster descent. --- Reset airbreak flag only when land (canExist goes false). --- Issue: it wouldn't reset if land in water, use fly, and launch from air, before I added test for water, --- not sure if there are other such cases. --- Temporarily add hud debug display to show descent velocity, gravity override, and airbreak flag. --- Still in process of tuning all the parameters. +local enable_hud_overlay = minetest.settings:get_bool("hangglider.enable_hud_overlay", true) +local enable_flak = has_areas and minetest.settings:get_bool("hangglider.enable_flak", true) +local flak_warning_time = tonumber(minetest.settings:get("hangglider.flak_warning_time")) or 2 +local hangglider_uses = tonumber(minetest.settings:get("hangglider.uses")) or 250 +local flak_warning = "You have entered restricted airspace!\n".. + "You will be shot down in "..flak_warning_time.." seconds by anti-aircraft guns!" --- Modifications by Piezo_ --- 2018-11-25 --- hud overlay and debug can be enabled/disabled --- Added blender-rendered overlay for struts using the actual model. --- Reduced airbreak penalty severity --- gave glider limited durability. --- Improved gravity adjustment function. --- Changed airbreaking process --- Removed airbreak penalty, as any 'advantage' seems minimal after new adjustments --- Removed airbreak until minetest devs are smart enough to implement better serverside players. --- Simplified liquid check. +local hanggliding_players = {} +local hud_overlay_ids = {} --- Modifications by gpcf --- 2018-12-09 --- get shot down while flying over protected areas marked as no-fly-zones (flak, from German Flugabwehrkanone) --- set these areas with the /area_flak command - --- Modifications by SpaghettiToastBook --- 2018-12-29 --- Physics overrides use player_monoids mod if available - --- Modifications by SwissalpS --- 2022-05-16 --- Add Z-index to theoretically be behind hotbar and practically behind other HUDs - -local HUD_Overlay = true -- show glider struts as overlay on HUD -local debug = false -- show debug info in top-center of hud -local warning_time = tonumber(minetest.settings:get("hangglider.flak_warning_time")) or 2 -hangglider = {} -- Make this global, so other mods can tell if hangglider exists. -hangglider.use = {} - -if HUD_Overlay then - hangglider.id = {} -- hud id for displaying overlay with struts -end - -if debug then -- hud id for debug data - hangglider.debug = {} -end - -if minetest.get_modpath("areas") then - hangglider.flak = true - -- chat command definition essentially copied from areas mod. - minetest.register_chatcommand("area_flak",{ +if enable_flak then + minetest.register_chatcommand("area_flak", { params = "", description = "Toggle airspace restrictions for area ", func = function(name, param) @@ -78,7 +22,6 @@ if minetest.get_modpath("areas") then if not id then return false, "Invalid usage, see /help area_flak." end - if not areas:isAreaOwner(id, name) then return false, "Area "..id.." does not exist or is not owned by you." end @@ -86,30 +29,81 @@ if minetest.get_modpath("areas") then -- Save false as nil to avoid inflating the DB. areas.areas[id].flak = open or nil areas:save() - return true, ("Area's airspace %s."):format(open and "closed" or "opened") + return true, "Area "..id.." airspace "..(open and "closed" or "opened") end }) end -hangglider.can_fly = function (pname, pos) - -- Checks if the player will get shot down at the position - if areas and hangglider.flak then - local flak = false - local owners = {} - for _, area in pairs(areas:getAreasAtPos(pos)) do - if area.flak then - flak = true - end - owners[area.owner] = true +local function set_hud_overlay(player, name, image) + if not enable_hud_overlay then + return + end + if not hud_overlay_ids[name] then + hud_overlay_ids[name] = player:hud_add({ + hud_elem_type = "image", + text = image, + position = {x = 0, y = 0}, + scale = {x = -100, y = -100}, + alignment = {x = 1, y = 1}, + offset = {x = 0, y = 0}, + z_index = -150 + }) + else + player:hud_change(hud_overlay_ids[name], "text", image) + end +end + +local function set_physics_overrides(player, overrides) + if has_player_monoids then + for name, value in pairs(overrides) do + player_monoids[name]:add_change(player, value, "hangglider:glider") end - if flak and not owners[pname] then - return false + else + player:set_physics_override(overrides) + end +end + +local function remove_physics_overrides(player) + for _, name in pairs({"jump", "speed", "gravity"}) do + if has_player_monoids then + player_monoids[name]:del_change(player, "hangglider:glider") + else + player:set_physics_override({[name] = 1}) end end +end + +local function can_fly(pos, name) + if not enable_flak then + return true + end + local flak = false + local owners = {} + for _, area in pairs(areas:getAreasAtPos(pos)) do + if area.flak then + flak = true + end + owners[area.owner] = true + end + if flak and not owners[name] then + return false + end return true end -hangglider.shot_sound = function (pos) +local function safe_node_below(pos) + local node = minetest.get_node_or_nil(vector.new(pos.x, pos.y - 0.5, pos.z)) + if not node then + return false + end + local def = minetest.registered_nodes[node.name] + if def and (def.walkable or (def.liquidtype ~= "none" and def.damage_per_second <= 0)) then + return true + end + return false +end + +local function shoot_flak_sound(pos) minetest.sound_play("hangglider_flak_shot", { pos = pos, max_hear_distance = 30, @@ -117,185 +111,135 @@ hangglider.shot_sound = function (pos) }, true) end -local has_player_monoids = minetest.get_modpath("player_monoids") - -local physics_attrs = {"jump", "speed", "gravity"} - -local function apply_physics_override(player, overrides) - if has_player_monoids then - for _, attr in pairs(physics_attrs) do - if overrides[attr] then - player_monoids[attr]:add_change(player, overrides[attr], "hangglider:glider") +local function hangglider_step(self, dtime) + local gliding = false + local player = self.object:get_attach("parent") + if player then + local pos = player:get_pos() + local name = player:get_player_name() + if hanggliding_players[name] then + if not safe_node_below(pos) then + gliding = true + local vel = player:get_velocity().y + if vel < 0 and vel > -3 then + set_physics_overrides(player, { + speed = math.abs(vel / 2.0) + 1.0, + gravity = (vel + 3) / 20, + }) + elseif vel <= -3 then + set_physics_overrides(player, { + speed = 2.5, + gravity = -0.1, + }) + if vel < -5 then + -- Extra airbrake when falling too fast + player:add_velocity(vector.new(0, math.min(5, math.abs(vel / 10.0)), 0)) + end + else -- vel > 0 + set_physics_overrides(player, { + speed = 1.0, + gravity = 0.25, + }) + end end + if not can_fly(pos, name) then + if not self.flak_timer then + self.flak_timer = 0 + shoot_flak_sound(pos) + minetest.chat_send_player(name, flak_warning) + else + self.flak_timer = self.flak_timer + dtime + end + if self.flak_timer > flak_warning_time then + player:set_hp(1, {type = "set_hp", cause = "hangglider:flak"}) + player:get_inventory():remove_item("main", ItemStack("hangglider:hangglider")) + shoot_flak_sound(pos) + gliding = false + end + end + if not gliding then + remove_physics_overrides(player) + hanggliding_players[name] = nil + set_hud_overlay(player, name, "blank.png") + end + end + end + if not gliding then + self.object:set_detach() + self.object:remove() + end +end + +local function hangglider_use(stack, player) + if type(player) ~= "userdata" then + return -- Real players only + end + local pos = player:get_pos() + local name = player:get_player_name() + if not hanggliding_players[name] then + minetest.sound_play("hanggliger_equip", {pos = pos, max_hear_distance = 8, gain = 1.0}, true) + local entity = minetest.add_entity(pos, "hangglider:glider") + if entity then + entity:set_attach(player, "", vector.new(0, 10, 0), vector.new(0, 0, 0)) + local color = stack:get_meta():get("hangglider_color") + if color then + entity:set_properties({ + textures = {"wool_white.png^[multiply:#"..color, "default_wood.png"} + }) + end + set_hud_overlay(player, name, "hangglider_overlay.png") + set_physics_overrides(player, {jump = 0, gravity = 0.25}) + hanggliding_players[name] = true + if hangglider_uses > 0 then + stack:add_wear(65535 / hangglider_uses) + end + return stack end else - player:set_physics_override(overrides) + set_hud_overlay(player, name, "blank.png") + remove_physics_overrides(player) + hanggliding_players[name] = nil end end -local function remove_physics_override(player, overrides) - for _, attr in pairs(physics_attrs) do - if overrides[attr] then - if has_player_monoids then - player_monoids[attr]:del_change(player, "hangglider:glider") - else - player:set_physics_override({[attr] = 1}) - end - end +minetest.register_on_dieplayer(function(player) + local name = player:get_player_name() + hanggliding_players[name] = nil + remove_physics_overrides(player) +end) + +minetest.register_on_leaveplayer(function(player) + local name = player:get_player_name() + hanggliding_players[name] = nil + hud_overlay_ids[name] = nil + remove_physics_overrides(player) +end) + +minetest.register_on_player_hpchange(function(player, hp_change, reason) + local name = player:get_player_name() + if hanggliding_players[name] and reason.type == "fall" then + -- Stop all fall damage when hanggliding + return 0, true end -end + return hp_change +end, true) minetest.register_entity("hangglider:glider", { visual = "mesh", visual_size = {x = 12, y = 12}, collisionbox = {0,0,0,0,0,0}, - mesh = "glider.obj", + mesh = "hangglider.obj", immortal = true, static_save = false, textures = {"wool_white.png", "default_wood.png"}, - on_step = function(self, dtime) - local canExist = false - if self.object:get_attach() then - local player = self.object:get_attach("parent") - if player then - local pos = player:get_pos() - local pname = player:get_player_name() - if hangglider.use[pname] then - local mrn_name = minetest.registered_nodes[minetest.get_node(vector.new(pos.x, pos.y-0.5, pos.z)).name] - if mrn_name then - if not (mrn_name.walkable or mrn_name.liquidtype ~= "none") then - canExist = true - local step_v = player:get_velocity().y - if step_v < 0 and step_v > -3 then - apply_physics_override(player, {speed = math.abs(step_v/2) + 0.75}) - elseif step_v <= -3 then -- Cap our gliding movement speed. - apply_physics_override(player, {speed = 2.25}) - else - remove_physics_override(player, {speed = 1}) - end - if debug then - player:hud_change(hangglider.debug[pname].id, "text", step_v..', '.. - player:get_physics_override().gravity) - end - apply_physics_override(player, {gravity = ((step_v + 3)/20)}) - end - end - end - if not hangglider.can_fly(pname,pos) then - if not self.warned then -- warning shot - self.warned = 0 - hangglider.shot_sound(pos) - minetest.chat_send_player(pname, "Protected area! You will be shot down in " .. - warning_time .. " seconds by anti-aircraft guns!") - end - self.warned = self.warned + dtime - if self.warned > warning_time then -- shoot down - player:set_hp(1) - player:get_inventory():remove_item("main", ItemStack("hangglider:hangglider")) - hangglider.shot_sound(pos) - canExist = false - end - end - if not canExist then - remove_physics_override(player, {gravity = 1, jump = 1, speed = 1}) - hangglider.use[pname] = false - if HUD_Overlay then - player:hud_change(hangglider.id[pname], "text", "blank.png") - end - end - end - end - if not canExist then - self.object:set_detach() - self.object:remove() - end - end + on_step = hangglider_step, }) -minetest.register_on_dieplayer(function(player) - remove_physics_override(player, { - gravity = 1, - jump = 1, - }) - hangglider.use[player:get_player_name()] = false -end) - -minetest.register_on_joinplayer(function(player) - local pname = player:get_player_name() - remove_physics_override(player, { - gravity = 1, - jump = 1, - }) - hangglider.use[pname] = false - if HUD_Overlay then - hangglider.id[pname] = player:hud_add({ - hud_elem_type = "image", - text = "blank.png", - position = {x = 0, y = 0}, - scale = {x = -100, y = -100}, - alignment = {x = 1, y = 1}, - offset = {x = 0, y = 0}, - z_index = -150 - }) - end - if debug then - hangglider.debug[pname] = {id = player:hud_add({hud_elem_type = "text", - position = {x = 0.5, y= 0.1 }, - text = "-", - number = 0xFF0000}), -- red text - -- ht = {50,50,50}, - } - end -end) - -minetest.register_on_leaveplayer(function(player) - local pname = player:get_player_name() - hangglider.use[pname] = nil - if HUD_Overlay then hangglider.id[pname] = nil end - if debug then hangglider.debug[pname] = nil end -end) - minetest.register_tool("hangglider:hangglider", { description = "Glider", - inventory_image = "glider_item.png", - stack_max = 1, - on_use = function(itemstack, player) - if not player or player.is_fake_player then - -- player does not exist or is created from an automated machine (fake_player) - return - end - local pos = player:get_pos() - local pname = player:get_player_name() - if not hangglider.use[pname] then -- Equip - minetest.sound_play("bedsheet", {pos = pos, max_hear_distance = 8, gain = 1.0}, true) - if HUD_Overlay then player:hud_change(hangglider.id[pname], "text", "glider_struts.png") end - minetest.add_entity(pos, "hangglider:glider"):set_attach(player, "", {x=0,y=10,z=0}, {x=0,y=0,z=0}) - hangglider.use[pname] = true - apply_physics_override(player, {jump = 0}) - itemstack:set_wear(itemstack:get_wear() + 255) - return itemstack - elseif hangglider.use[pname] then -- Unequip - if HUD_Overlay then player:hud_change(hangglider.id[pname], "text", "default_wood.png^[colorize:#0000:255") end - hangglider.use[pname] = false - end - end, + inventory_image = "hangglider_item.png", sound = {breaks = "default_tool_breaks"}, + on_use = hangglider_use, }) -minetest.register_craft({ - type = "shapeless", - output = "hangglider:hangglider", - recipe = {"default:paper", "default:paper", "default:paper", - "default:paper", "hangglider:hangglider", "default:paper", - "default:paper", "default:paper", "default:paper" - }, -}) - -minetest.register_craft({ - output = "hangglider:hangglider", - recipe = { - {"wool:white", "wool:white", "wool:white"}, - {"default:stick", "", "default:stick"}, - {"", "default:stick", ""}, - } -}) +dofile(minetest.get_modpath("hangglider").."/crafts.lua") diff --git a/mod.conf b/mod.conf index 34c5994..18b6b39 100644 --- a/mod.conf +++ b/mod.conf @@ -1,4 +1,5 @@ name = hangglider -depends = default, wool -optional_depends = areas, player_monoids -min_minetest_version = 5.0 \ No newline at end of file +description = Adds a functional hang glider for exploring +depends = default, wool, dye +optional_depends = areas, player_monoids, unifieddyes +min_minetest_version = 5.0 diff --git a/models/glider.obj b/models/hangglider.obj old mode 100755 new mode 100644 similarity index 100% rename from models/glider.obj rename to models/hangglider.obj diff --git a/settingtypes.txt b/settingtypes.txt index 6aef999..40e85d2 100644 --- a/settingtypes.txt +++ b/settingtypes.txt @@ -1,3 +1,11 @@ -# How long (in seconds) before hang gliders get shot down when flying over -# protected areas +# The number of times the hangglider can be used before breaking. Set to 0 for infinite uses. +hangglider.uses (Hangglider uses) int 250 + +# If true, an image of the hangglider struts is shown on the hud when gliding. +hangglider.enable_hud_overlay (Enable overlay) bool true + +# If enabled, and the `areas` mod is installed, enables airspace restrictions to be added to areas. +hangglider.enable_flak (Enable flak) bool true + +# Time in seconds before hanggliders get shot down when flying in restricted airspace hangglider.flak_warning_time (Flak warning time) float 2 diff --git a/sounds/bedsheet.ogg b/sounds/hanggliger_equip.ogg similarity index 100% rename from sounds/bedsheet.ogg rename to sounds/hanggliger_equip.ogg diff --git a/textures/glider_item.png b/textures/glider_item.png deleted file mode 100755 index 2287a54..0000000 Binary files a/textures/glider_item.png and /dev/null differ diff --git a/textures/glider_struts.png b/textures/glider_struts.png deleted file mode 100644 index da71a8a..0000000 Binary files a/textures/glider_struts.png and /dev/null differ diff --git a/textures/hangglider_color.png b/textures/hangglider_color.png new file mode 100644 index 0000000..ac2a8c7 Binary files /dev/null and b/textures/hangglider_color.png differ diff --git a/textures/hangglider_item.png b/textures/hangglider_item.png new file mode 100644 index 0000000..657627a Binary files /dev/null and b/textures/hangglider_item.png differ diff --git a/textures/hangglider_overlay.png b/textures/hangglider_overlay.png new file mode 100644 index 0000000..2601fb1 Binary files /dev/null and b/textures/hangglider_overlay.png differ