Merge pull request 'Fix the FOV issues with Bows, Sprinting and Spyglasses.' (#4045) from Fix-FOV into master

Reviewed-on: https://git.minetest.land/MineClone2/MineClone2/pulls/4045
Reviewed-by: the-real-herowl <the-real-herowl@noreply.git.minetest.land>
This commit is contained in:
Michieal 2023-12-18 23:19:43 +00:00
commit 56ebb5ac09
10 changed files with 365 additions and 36 deletions

@ -33,6 +33,15 @@ local bow_load = {}
-- Another player table, this one stores the wield index of the bow being charged -- Another player table, this one stores the wield index of the bow being charged
local bow_index = {} local bow_index = {}
-- define FOV modifier(s)
mcl_fovapi.register_modifier({
name = "bowcomplete",
fov_factor = 0.8,
time = 1,
reset_time = 0.3,
is_multiplier = true,
})
function mcl_bows.shoot_arrow(arrow_item, pos, dir, yaw, shooter, power, damage, is_critical, bow_stack, collectable) function mcl_bows.shoot_arrow(arrow_item, pos, dir, yaw, shooter, power, damage, is_critical, bow_stack, collectable)
local obj = minetest.add_entity({x=pos.x,y=pos.y,z=pos.z}, arrow_item.."_entity") local obj = minetest.add_entity({x=pos.x,y=pos.y,z=pos.z}, arrow_item.."_entity")
if power == nil then if power == nil then
@ -183,6 +192,9 @@ end
-- Resets the bow charging state and player speed. To be used when the player is no longer charging the bow -- Resets the bow charging state and player speed. To be used when the player is no longer charging the bow
local function reset_bow_state(player, also_reset_bows) local function reset_bow_state(player, also_reset_bows)
-- clear the FOV change from the player.
mcl_fovapi.remove_modifier(player, "bowcomplete") -- for the complete zoom in FOV Modifier.
bow_load[player:get_player_name()] = nil bow_load[player:get_player_name()] = nil
bow_index[player:get_player_name()] = nil bow_index[player:get_player_name()] = nil
if minetest.get_modpath("playerphysics") then if minetest.get_modpath("playerphysics") then
@ -314,6 +326,9 @@ controls.register_on_hold(function(player, key, time)
end end
bow_load[name] = minetest.get_us_time() bow_load[name] = minetest.get_us_time()
bow_index[name] = player:get_wield_index() bow_index[name] = player:get_wield_index()
-- begin Bow Zoom.
mcl_fovapi.apply_modifier(player, "bowcomplete")
else else
if player:get_wield_index() == bow_index[name] then if player:get_wield_index() == bow_index[name] then
if type(bow_load[name]) == "number" then if type(bow_load[name]) == "number" then

@ -1,6 +1,6 @@
name = mcl_bows name = mcl_bows
author = Arcelmi author = Arcelmi
description = This mod adds bows and arrows for MineClone 2. description = This mod adds bows and arrows for MineClone 2.
depends = controls, mcl_particles, mcl_enchanting, mcl_init, mcl_util, mcl_shields depends = controls, mcl_particles, mcl_enchanting, mcl_init, mcl_util, mcl_shields, mcl_fovapi
optional_depends = awards, mcl_achievements, mcl_core, mcl_mobitems, playerphysics, doc, doc_identifier, mesecons_button optional_depends = awards, mcl_achievements, mcl_core, mcl_mobitems, playerphysics, doc, doc_identifier, mesecons_button

@ -17,6 +17,15 @@ minetest.register_craft({
} }
}) })
mcl_fovapi.register_modifier({
name = "spyglass",
fov_factor = 8,
time = 0.1,
reset_time = 0,
is_multiplier = false,
exclusive = true,
})
local spyglass_scope = {} local spyglass_scope = {}
local function add_scope(player) local function add_scope(player)
@ -37,7 +46,8 @@ local function remove_scope(player)
player:hud_remove(spyglass_scope[player]) player:hud_remove(spyglass_scope[player])
spyglass_scope[player] = nil spyglass_scope[player] = nil
player:hud_set_flags({wielditem = true}) player:hud_set_flags({wielditem = true})
player:set_fov(86.1) mcl_fovapi.remove_modifier(player, "spyglass") -- use the api to remove the FOV effect.
-- old code: player:set_fov(86.1)
end end
end end
@ -55,7 +65,8 @@ controls.register_on_hold(function(player, key, time)
if key ~= "RMB" then return end if key ~= "RMB" then return end
local wielditem = player:get_wielded_item() local wielditem = player:get_wielded_item()
if wielditem:get_name() == "mcl_spyglass:spyglass" then if wielditem:get_name() == "mcl_spyglass:spyglass" then
player:set_fov(8, false, 0.1) mcl_fovapi.apply_modifier(player, "spyglass") -- apply the FOV effect.
-- old code: player:set_fov(8, false, 0.1)
if spyglass_scope[player] == nil then if spyglass_scope[player] == nil then
add_scope(player) add_scope(player)
end end

@ -1,4 +1,4 @@
name = mcl_spyglass name = mcl_spyglass
author = NO11 author = NO11
description = This mod adds a spyglass, which is an item that can be used for zooming in on specific locations. description = This mod adds a spyglass, which is an item that can be used for zooming in on specific locations.
depends = mcl_core, controls depends = mcl_core, controls, mcl_fovapi

@ -0,0 +1,82 @@
### FOV API
<!-- TOC -->
* [FOV API](#fov-api)
* [Description](#description)
* [Troubleshooting](#troubleshooting)
* [Modifier Definition](#modifier-definition-)
* [Global MCL_FOVAPI Tables](#global-mclfovapi-tables)
* [Namespaces](#namespaces)
* [Functions](#functions)
<!-- TOC -->
#### Description
This API defines and applies different Field Of View effects to players via MODIFIERS.
#### Troubleshooting
In the `init.lua` file for this module, there is a `DEBUG` variable at the top that will turn on logging.
Use it to see what is going on.
#### Modifier Definition
```lua
def = {
name = name,
fov_factor = fov_factor,
time = time,
reset_time = reset_time,
is_multiplier = is_multiplier,
exclusive = exclusive,
on_start = on_start,
on_end = on_end,
}
```
* Name: The name of the Modifier, used to identify the specific modifier. Case sensitive.
* FOV Factor: A float value defining the FOV to apply. Can be an absolute or percentage, depending on Exclusive and
Is_Multiplier.
* Time: A float value defining the number of seconds to take when applying the FOV Factor.
Used to smoothly move between FOVs. Use 0 for an immediate FOV Shift. (Transition time.)
* Reset Time: A float value defining the number of seconds to take when removing the FOV Factor.
Used to smoothly move between FOVs. Use 0 for an immediate FOV Shift. (Reset transition time.)
Defaults to `time` if not defined.
* Is Multiplier: A bool value used to specify if the FOV Factor is an absolute FOV value or if it should be a percentage
of the current FOV. Defaults to `true` if not defined.
* Exclusive: A bool value used to specify whether the modifier will override all other FOV modifiers. An example of this
is how the spy glass sets the FOV to be a specific value regardless of any other FOV effects applied. Defaults to
`false` if not defined.
* On Start: the `on_start` is a callback function `on_start(player)` that is called if defined. The parameter `player`
is a ref to the player that had the modifier applied. Called from `mcl_fovapi.apply_modifier` immediately after
the FOV Modifier has been applied.
* On End: the `on_end` is a callback function `on_end(player)` that is called if defined. The parameter `player`
is a ref to the player that had the modifier applied. Called from `mcl_fovapi.remove_modifier` immediately after
the FOV Modifier has been removed.
Note: passing incorrect values in the definition will have unintended consequences.
#### Global MCL_FOVAPI Tables
There are three tables that are accessible via the API. They are `registered_modifiers` and `applied_modifiers`.
`mcl_fovapi.registered_modifiers` has the definitions of all the registered FOV Modifiers. Indexed by Modifier Name.
And, `mcl_fovapi.applied_modifiers` is indexed by the Player Name. It contains the names of all the modifiers applied to the
player.
#### Namespaces
`mcl_fovapi` is the default API Namespace.
#### Functions
`mcl_fovapi.register_modifier(def)`
Used to register a new FOV Modifier for use. Must be called before applying said modifier to a player.
See Modifier Definition for what the parameters are.
`mcl_fovapi.apply_modifier(player, modifier_name)`
Used to apply a registered FOV modifier to a player. Takes a reference to the player and the modifier's name (string).
`mcl_fovapi.remove_modifier(player, modifier_name)`
Used to remove a specific FOV modifier from a Player. Takes a reference to the player and the modifier's name (string).
Removed immediately.
`mcl_fovapi.remove_all_modifiers(player)`
Used to remove all FOV modifiers from a Player. Takes a reference to the Player. FOV change is instantaneous.

@ -0,0 +1,232 @@
---
--- Copyright 2023, Michieal.
--- License: GPL3. (Default Mineclone2 License)
--- Created by michieal.
--- DateTime: 12/2/23 5:47 AM
---
-- Locals (and cached)
local DEBUG = false -- debug constant for troubleshooting.
local pairs = pairs
-- Globals
mcl_fovapi = {}
mcl_fovapi.registered_modifiers = {}
mcl_fovapi.applied_modifiers = {}
minetest.register_on_joinplayer(function(player)
local player_name = player:get_player_name()
-- initialization
mcl_fovapi.applied_modifiers[player_name] = {}
end)
minetest.register_on_leaveplayer(function(player)
local player_name = player:get_player_name()
-- handle clean up
mcl_fovapi.applied_modifiers[player_name] = nil
end)
function mcl_fovapi.register_modifier(def)
if type(def.name) ~= "string" then
error("Modifier name must be a string")
end
if type(def.fov_factor) ~= "number" then
error("FOV factor must be a number")
end
if type(def.time) ~= "number" then
error("Transition time must be a number")
end
if def.reset_time ~= nil and type(def.reset_time) ~= "number" then
error("Reset time, if provided, must be a number")
end
if def.on_start ~= nil and type(def.on_start) ~= "function" then
error("Callback on_start must be a function")
end
if def.on_end ~= nil and type(def.on_end) ~= "function" then
error("Callback on_end must be a function")
end
local mdef = {}
mdef.fov_factor = def.fov_factor
mdef.time = def.time
mdef.reset_time = def.reset_time or def.time
if def.is_multiplier == false then mdef.is_multiplier = false
else mdef.is_multiplier = true end
if def.exclusive == true then mdef.exclusive = true
else mdef.exclusive = false end
mdef.on_start = def.on_start
mdef.on_end = def.on_end
if DEBUG then
minetest.log("FOV::Modifier Definition Registered:\n" .. dump(def))
end
mcl_fovapi.registered_modifiers[def.name] = mdef
end
minetest.register_on_respawnplayer(function(player)
mcl_fovapi.remove_all_modifiers(player)
end)
function mcl_fovapi.apply_modifier(player, modifier_name)
if not player or not modifier_name then
return
end
if mcl_fovapi.registered_modifiers[modifier_name] == nil then
return
end
local player_name = player:get_player_name()
if mcl_fovapi.applied_modifiers and mcl_fovapi.applied_modifiers[player_name] and mcl_fovapi.applied_modifiers[player_name][modifier_name] then
return
end
for k, _ in pairs(mcl_fovapi.applied_modifiers[player_name]) do
if mcl_fovapi.registered_modifiers[k].exclusive == true then return end
end
local modifier = mcl_fovapi.registered_modifiers[modifier_name]
if modifier.on_start then
modifier.on_start(player)
end
mcl_fovapi.applied_modifiers[player_name][modifier_name] = true -- set the applied to be true.
if DEBUG then
minetest.log("FOV::Player Applied Modifiers :" .. dump(mcl_fovapi.applied_modifiers[player_name]))
end
if DEBUG then
minetest.log("FOV::Modifier applied to player:" .. player_name .. " modifier: " .. modifier_name)
end
-- modifier apply code.
if modifier.exclusive == true then
-- if exclusive, reset the player's fov, and apply the new fov.
if modifier.is_multiplier then
player:set_fov(0, false, 0)
end
player:set_fov(modifier.fov_factor, modifier.is_multiplier, modifier.time)
else
-- not exclusive? let's apply it in the mix.
local fov_factor, is_mult = player:get_fov()
if fov_factor == 0 then
fov_factor = 1
is_mult = true
end
if modifier.is_multiplier or is_mult then
fov_factor = fov_factor * modifier.fov_factor
else
fov_factor = (fov_factor + modifier.fov_factor) / 2
end
if modifier.is_multiplier and is_mult then
player:set_fov(fov_factor, true, modifier.time)
else
player:set_fov(fov_factor, false, modifier.time)
end
end
end
function mcl_fovapi.remove_modifier(player, modifier_name)
if not player or not modifier_name then
return
end
local player_name = player:get_player_name()
if not mcl_fovapi.applied_modifiers[player_name]
or not mcl_fovapi.applied_modifiers[player_name][modifier_name] then
return
end
if DEBUG then
minetest.log("FOV::Player: " .. player_name .. " modifier: " .. modifier_name .. "removed.")
end
mcl_fovapi.applied_modifiers[player_name][modifier_name] = nil
local modifier = mcl_fovapi.registered_modifiers[modifier_name]
-- check for other fov modifiers, and set them up, or reset to default.
local applied = {}
for k, _ in pairs(mcl_fovapi.applied_modifiers[player_name]) do
applied[k] = mcl_fovapi.registered_modifiers[k]
end
local elem = next
if elem(applied) == nil then
player:set_fov(0, false, modifier.reset_time)
return
end
local exc = false
for k, _ in pairs(applied) do
if applied[k].exclusive == true then
exc = applied[k]
break
end
end
-- handle exclusives.
if exc ~= false then
player:set_fov(exc.fov_factor, exc.is_multiplier, 0) -- we want this to be immediate.
else
-- handle normal fov modifiers.
local fov_factor = 1
local non_multiplier_added = false
for _, x in pairs(applied) do
if not x.is_multiplier then
if non_multiplier_added then
fov_factor = (fov_factor + x.fov_factor) / 2
else
non_multiplier_added = true
fov_factor = fov_factor * x.fov_factor
end
else
fov_factor = fov_factor * x.fov_factor
end
end
player:set_fov(fov_factor, not non_multiplier_added, modifier.reset_time)
end
if mcl_fovapi.registered_modifiers[modifier_name].on_end then
mcl_fovapi.registered_modifiers[modifier_name].on_end(player)
end
end
function mcl_fovapi.remove_all_modifiers(player)
if not player then
return
end
local player_name = player:get_player_name()
if DEBUG then
minetest.log("FOV::Player: " .. player_name .. " modifiers have been reset.")
end
for name, x in pairs(mcl_fovapi.applied_modifiers[player_name]) do
x = nil
if mcl_fovapi.registered_modifiers[name].on_end then
mcl_fovapi.registered_modifiers[name].on_end(player)
end
end
player:set_fov(0, false, 0)
end
--[[
Notes:
set_fov(fov, is_multiplier, transition_time): Sets player's FOV
fov: FOV value.
is_multiplier: Set to true if the FOV value is a multiplier. Defaults to false.
transition_time: If defined, enables smooth FOV transition. Interpreted as the time (in seconds) to reach target FOV.
If set to 0, FOV change is instantaneous. Defaults to 0.
Set fov to 0 to clear FOV override.
--]]

@ -0,0 +1,4 @@
name = mcl_fovapi
author = Michieal, Herowl
description = An API for handling FOV changes.
depends = mcl_player

@ -177,7 +177,7 @@ minetest.register_on_joinplayer(function(player)
player_textures[name] = { "character.png", "blank.png", "blank.png" } player_textures[name] = { "character.png", "blank.png", "blank.png" }
--player:set_local_animation({x=0, y=79}, {x=168, y=187}, {x=189, y=198}, {x=200, y=219}, 30) --player:set_local_animation({x=0, y=79}, {x=168, y=187}, {x=189, y=198}, {x=200, y=219}, 30)
player:set_fov(86.1) -- see <https://minecraft.gamepedia.com/Options#Video_settings>>>> -- player:set_fov(86.1) -- see <https://minecraft.gamepedia.com/Options#Video_settings>>>>
end) end)
minetest.register_on_leaveplayer(function(player) minetest.register_on_leaveplayer(function(player)

@ -64,40 +64,24 @@ local function cancelClientSprinting(name)
players[name].clientSprint = false players[name].clientSprint = false
end end
mcl_fovapi.register_modifier({
name = "sprint",
fov_factor = 1.1,
time = 0.15,
is_multiplier = true,
})
local function setSprinting(playerName, sprinting) --Sets the state of a player (0=stopped/moving, 1=sprinting) local function setSprinting(playerName, sprinting) --Sets the state of a player (0=stopped/moving, 1=sprinting)
if not sprinting and not mcl_sprint.is_sprinting(playerName) then return end if not sprinting and not mcl_sprint.is_sprinting(playerName) then return end
local player = minetest.get_player_by_name(playerName) local player = minetest.get_player_by_name(playerName)
local controls = player:get_player_control()
if players[playerName] then if players[playerName] then
players[playerName].sprinting = sprinting players[playerName].sprinting = sprinting
local fov_old = players[playerName].fov if sprinting then
local fov_new = fov_old
local fade_time = .15
if sprinting == true
or controls.RMB
and string.find(player:get_wielded_item():get_name(), "mcl_bows:bow")
and player:get_wielded_item():get_name() ~= "mcl_bows:bow" then
if sprinting == true then
fov_new = math.min(players[playerName].fov + 0.05, 1.2)
else
fov_new = .7
players[playerName].fade_time = .3
end
if sprinting == true then
playerphysics.add_physics_factor(player, "speed", "mcl_sprint:sprint", mcl_sprint.SPEED) playerphysics.add_physics_factor(player, "speed", "mcl_sprint:sprint", mcl_sprint.SPEED)
end mcl_fovapi.apply_modifier(player, "sprint")
elseif sprinting == false else
and player:get_wielded_item():get_name() ~= "mcl_bows:bow_0"
and player:get_wielded_item():get_name() ~= "mcl_bows:bow_1"
and player:get_wielded_item():get_name() ~= "mcl_bows:bow_2" then
fov_new = math.max(players[playerName].fov - 0.05, 1.0)
if sprinting == false then
playerphysics.remove_physics_factor(player, "speed", "mcl_sprint:sprint") playerphysics.remove_physics_factor(player, "speed", "mcl_sprint:sprint")
end mcl_fovapi.remove_modifier(player, "sprint")
end
if fov_new ~= fov_old then
players[playerName].fov = fov_new
player:set_fov(fov_new, true, fade_time)
end end
return true return true
end end

@ -1,4 +1,5 @@
name = mcl_sprint name = mcl_sprint
author = GunshipPenguin author = GunshipPenguin
description = Allows the player to sprint by pressing the “Use” key (default: E). description = Allows the player to sprint by pressing the “AUX” key (default: E).
depends = mcl_playerinfo, playerphysics, mcl_hunger depends = mcl_playerinfo, playerphysics, mcl_hunger, mcl_fovapi
optional = mcl_bows