Various adjustments and fixes

This commit is contained in:
Till Affeldt 2020-05-12 16:00:24 +02:00
parent 4df9a61374
commit f42b4183e5
15 changed files with 298 additions and 76 deletions

152
api_doc.md Normal file
View File

@ -0,0 +1,152 @@
# API Documentation
## How to read this document
If a function states multiple parameters of the same name then either of them has to be passed on function call. Look at the function signature to determine the order of required parameters.
## Custom Weather Registration
### Register Weather Preset
``climate_api.register_weather(name, conditions, effects)``
Invoke this function in order to create and register a new weather preset. Presets control which effects are to be applied to a player under specific circumstances. As an example, a preset for rain could require a high level of humidity and apply particle and sound effects.
__Parameters__:
- ``name <string>``: A unique identifier resembling the new weather preset.
Should be prefixed with the mod's name in a way that could look like ``mymod:awesome_weather``. This name should only be used once.
- ``conditions <table>``: An associative array that checks weather influences for specified values. Keys should be the name of an influence, values should be of matching type for repective influence. Keys can be prefixed with ``min_`` to accept any value equal or higher than influence value. A prefix of ``max_`` will accept any value lesser than influence value. A prefix of ``has_`` can be used in conjunction with a numeric array as a value. It will accept any influence value that is present in the specified table. Omitting a prefix will accept the specified value only. All table entries have to be matched positively for the weather preset to be applied.
- ``conditions <function>``: For more control, a function can be specified instead of a conditions table. The function will receive a table as its first parameter, consisting of key-value pairs indicating the current value for each weather influence. The function is expected to return true if the weather preset is to be applied or false otherwise.
- ``effects <table>``: An associative array indicating which weather effects are to be applied whenever the weather preset is active. The key should be a registered weather effect name. The value will be passed as a parameter to the effect. Look at the documentation of individual effects to determine valid values.
- ``effects <function>``: A generator function that returns a set of weather effects and its parameters. This function will receive a table as its first parameter, consisting of key-value pairs indicating the current value for each weather influence. It is expected to return a table in the same fashion as ``effects <table>`` would have looked like.
__Returns__: ``nil``
### Register Weather Effect
``climate_api.register_effect(name, handler, htype)``
__Parameters__:
- ``name <string>``: A unique identifier resembling the new weather effect.
Should be prefixed with the mod's name in a way that could look like ``mymod:special_effect``. Call this function multiple times with the same name in order to apply multiple handler methods to the same effect.
- ``handler <function>``: This function will be called whenever players are affected by the registered effect. It receives a single parameter containing an accociative array. The keys represent player names and the values represent the set of applied preset parameters. This set is an accociative array as well with the keys representing the names of applied weather presets and the values representing the supplied data from those presets. This parameter could look like this:
``{ singleplayer = {"mymod:awesome_weather" = "a", "mymod:amazing_weather" = "b"} }``. If ``htype`` is ``tick`` then the list of players represents all currently affected players. If it is ``start`` or ``end`` then the list contains all players going though that change.
- ``htype <"start" | "tick" | "end">``: Determines when the handler will be called. ``start`` results in a callback whenever an effect is applied to at least one player, meaning that the very first weather preset applies it. ``tick`` results in a callback every update cycle as long as at least one player has the effect from at least one preset. ``end`` results in a callback whenever at least one player loses the effect completely.
__Returns__: ``nil``
### Set Update Cycle for Effect
``climate_api.set_effect_cycle(name, cycle)``
__Parameters__:
- ``name <string>``: The identifier of a registered weather effect
- ``cycle <number>``: The minimal time between update calls to registered effect handlers in seconds. This value defaults to ``climate_api.DEFAULT_CYCLE`` (2.0s). Other values are ``climate_api.SHORT_CYCLE`` (0s) for frequent update calls (like for particle effects) and ``climate_api.LONG_CYCLE`` (5.0s) for ressource intensive tasks and timed effects (like lightning strikes). You can also use any other custom number representing the amount of time in seconds.
__Returns__: ``nil``
### Register Global Environment Influence
``climate_api.register_global_influence(name, func)``
__Parameters__:
- ``name <string>`` A unique name identifying the registered influence
- ``func <function>``: A generator function that returns some value which is in turn supplied to weather presets and can be used as a condition.
__Returns__: ``nil``
### Register Local Environment Influence
``climate_api.register_influence(name, func)``
__Parameters__:
- ``name <string>`` A unique name identifying the registered influence
- ``func <function>``: A generator function that returns some value which is in turn supplied to weather presets and can be used as a condition. This function will receive a vector as its single parameter indicating the current position.
__Returns__: ``nil``
### Register Active Block Modifier
``climate_api.register_abm(config)``
## Environment Access
### Get Temperature At Position
``climate_api.environment.get_heat(pos)``
__Parameter__: ``pos <vector>``: Coordinates of requested location
__Returns__: ``<number>`` indicating current temperature in °F
### Get Humidity At Position
``climate_api.environment.get_humidity(pos)``
__Parameter__: ``pos <vector>``: Coordinates of requested location
__Returns__: ``<number>`` indicating current humidity
### Get Current Windspeed
``climate_api.environment.get_wind()``
__Returns__: ``<vector>`` indicating speed and direction
### ``climate_api.environment.get_weather_presets(player)``
### ``climate_api.environment.get_effects(player)``
## Skybox Modification
### ``climate_api.skybox.add(playername, name, sky)``
### ``climate_api.skybox.remove(playername, name)``
### ``climate_api.skybox.update(playername)``
## Player Physics Modifications
Climate API provides an easy way of modfying player physics in a compatible way.
The API is similar to those of ``player_monoids`` or ``playerphysics`` because it also uses multiplication to account for multiple modifiers.
In fact, these functions use ``player_monoids`` under the hud if that mod is available. If not, they will fall back to ``playerphysics``, ``pova``, or native overrides in that order.
### Add Physics Modifier
``climate_api.player_physics.add(id, player, effect, value)``
Register a new modifier that will be multiplied with the current value to set the new physics factor. Call this function again with the same id in order to change an existing modifier.
__Parameters__:
- ``id <string>``: A unique name used to identify the modifier. Should be prefixed with the mod's name.
- ``player <ObjectRef>``: The player affected by the physics change
- ``effect <"speed" | "jump" | "gravity">``: The type of physics to be changed
- ``value <number>``: The multiplicator. Use values between 0 and 1 to reduce physics attribute. Use values above 1 to increase it.
__Returns__: ``nil``
### Remove Physics Modifier
``climate_api.player_physics.remove(id, player, effect)``
Use this function to completely remove a physics modifer from the attribute calculation.
__Parameters__:
- ``id <string>``: The name used in ``player_physics.add`` that identifies a registered modifier
- ``player <ObjectRef>``: The player affected by the physics change
- ``effect <"speed" | "jump" | "gravity">``: The type of physics to be changed
__Returns__: ``nil``
## Utility Functions
### Merge Tables
``climate_api.utility.merge_tables(a, b)``
This function will merge two given accociative tables and return the result.
If in conflict, attributes of table B will override those of table A.
This is especially useful when assigning default values to a specified configuration with possibly missing entries.
Note that this function will also modify table A. If you want to prevent that, you should copy the table first: ``climate_api.utility.merge_tables(table.copy(a), b)``.
__Parameters__:
- ``a <table>``: The base table consisting of default values or other data
- ``b <table>``: The prioritized table to merge with, and possibly override A
__Returns__: ``<table>`` consisting of all attributes from A and B.
### ``climate_api.utility.sigmoid(value, max, growth, midpoint)``
### ``climate_api.utility.normalized_cycle(value)``

View File

@ -3,7 +3,7 @@
Use this effect to damage a player during dangerous weather events.
Expects a table as the parameter containing the following values:
- value <int> [1]: The amount of damage to be applied per successful roll.
- chance <int> [1]: Defines a 1/x roll per cycle for the player to get damaged. Higher values result in less frequent damage.
- rarity <int> [1]: Defines a 1/x chance per cycle for the player to get damaged. Higher values result in less frequent damage.
- check <table> [nil]: Use an additional outdoors check before applying damage. Consists of the following values:
- type <"light"|"raycast"> ["light"] (Whether the light level should be used a raycast should be performed)
- height <number> [0] (Height offset of weather origin from the player. Only used for raycasts)
@ -58,9 +58,9 @@ end
local function calc_damage(player, dmg)
if dmg.value == nil then dmg.value = 1 end
if dmg.chance == nil then dmg.chance = 1 end
if dmg.rarity == nil then dmg.rarity = 1 end
-- check if damage should be applied
if rng:next(1, dmg.chance) ~= 1 then return 0 end
if rng:next(1, dmg.rarity) ~= 1 then return 0 end
if dmg.check ~= nil then
-- check for obstacles in the way
if not check_hit(player, dmg.check) then return 0 end
@ -81,4 +81,3 @@ local function handle_effect(player_data)
end
climate_api.register_effect(EFFECT_NAME, handle_effect, "tick")
climate_api.set_effect_cycle(EFFECT_NAME, climate_api.MEDIUM_CYCLE)

View File

@ -95,4 +95,3 @@ end
climate_api.register_effect(EFFECT_NAME, start_effect, "start")
climate_api.register_effect(EFFECT_NAME, handle_effect, "tick")
climate_api.register_effect(EFFECT_NAME, stop_effect, "stop")
climate_api.set_effect_cycle(EFFECT_NAME, climate_api.MEDIUM_CYCLE)

View File

@ -10,29 +10,33 @@ Furthermore, the following default values have been changed:
- collisiondetection <bool> [true]
- collision_removal <bool> [true]
- playername <string> [current player] (Set to empty string to show for everyone)
- vertical <bool> [nil] (Unless explicitly set, particle facing rotation will be automatically set based on direction of velocity)
The following optional values have been introduced or expanded for convenience:
- size <int> [nil] (Overrides both minsize and maxsize if set)
The following optional values have been introduced for convenience:
- boxsize <vector> [nil] (Overrides minpos and maxpos based on specified sizes per direction with the player in the center)
- boxsize <number> [nil] (If set to a number, the resulting vector will have the specified size in all directions)
- v_offset <int> [0] (Use in conjunctin with boxsize. Adds specified height to minpos and maxpos y-coordinates)
- attach_to_player <bool> [false] (Overrides attached object with current player)
The following optional values have been expanded with additional value types for convenience:
- size <int> [nil] (Overrides both minsize and maxsize if set)
- minvel <int> [nil] (Overrides minvel with a downward facing vector of specified length)
- maxvel <int> [nil] (Overrides maxvel with a downward facing vector of specified length)
- velocity <vector | int> [nil] (Overrides both minvel and maxvel if set)
- minacc <int> [nil] (Overrides minacc with a downward facing vector of specified length)
- maxacc <int> [nil] (Overrides maxacc with a downward facing vector of specified length)
- acceleration <vector | int> [nil] (Overrides both minacc and maxacc if set)
- attach_to_player <bool> [false] (Overrides attached object with current player)
The following new behaviours have been introduced:
- use_wind <bool> [true] (Adjusts velocity and position for current windspeed)
- detached <bool> [false] (Unless enabled, considers positions as relative to current player like being attached)
- vertical <bool> [nil] (Unless explicitly set, will be automatically set based on direction of velocity)
- detach <bool> [false] (Unless enabled, considers positions as relative to current player as if spawner's position would be attached)
- adjust_for_velocity <bool> [true] (Corrects position of particle spawner by player's movement speed. Only applicable if detach = false and not manually attached)
]]
if not climate_mod.settings.particles then return end
local EFFECT_NAME = "climate_api:particles"
local CYCLE_LENGTH = climate_api.SHORT_CYCLE
-- parse config by injecting default values and adding additional parameters
local function parse_config(player, particles)
@ -44,7 +48,8 @@ local function parse_config(player, particles)
playername = player:get_player_name(),
use_wind = true,
attach_to_player = false,
detached = false
detach = false,
adjust_for_velocity = true
}
-- inject missing default values into specified config
@ -153,12 +158,20 @@ local function parse_config(player, particles)
config.attach_to_player = nil
-- attach coordinates to player unless specified or already attached
if (not config.detached) and config.attached == nil then
if (not config.detach) and config.attached == nil then
local ppos = player:get_pos()
config.minpos = vector.add(config.minpos, ppos)
config.maxpos = vector.add(config.maxpos, ppos)
-- correct spawn coordinates to adjust for player movement
if config.adjust_for_velocity then
local velocity = player:get_player_velocity()
config.minpos = vector.add(config.minpos, velocity)
config.maxpos = vector.add(config.maxpos, velocity)
end
config.detached = nil
end
config.detach = nil
config.adjust_for_velocity = nil
-- move particles in wind direction
if config.use_wind then
@ -198,4 +211,4 @@ local function handle_effect(player_data)
end
climate_api.register_effect(EFFECT_NAME, handle_effect, "tick")
climate_api.set_effect_cycle(EFFECT_NAME, climate_api.SHORT_CYCLE)
climate_api.set_effect_cycle(EFFECT_NAME, CYCLE_LENGTH)

View File

@ -18,26 +18,26 @@ local function handle_effect(player_data, prev_data)
for playername, data in pairs(prev_data) do
for weather, _ in pairs(data) do
if player_data[playername] == nil or player_data[playername][weather] == nil then
climate_api.skybox.remove_layer(playername, weather)
climate_api.skybox.remove(playername, weather)
end
end
end
for playername, data in pairs(player_data) do
for weather, value in pairs(data) do
climate_api.skybox.add_layer(playername, weather, value)
climate_api.skybox.add(playername, weather, value)
end
climate_api.skybox.update_skybox(playername)
climate_api.skybox.update(playername)
end
end
local function remove_effect(player_data)
for playername, data in pairs(player_data) do
for weather, _ in pairs(data) do
climate_api.skybox.remove_layer(playername, weather)
climate_api.skybox.remove(playername, weather)
end
climate_api.skybox.update(playername)
end
end
climate_api.register_effect(EFFECT_NAME, handle_effect, "tick")
climate_api.register_effect(EFFECT_NAME, remove_effect, "stop")
climate_api.set_effect_cycle("climate_api:skybox", climate_api.MEDIUM_CYCLE)

View File

@ -60,4 +60,3 @@ end
climate_api.register_effect(EFFECT_NAME, start_effect, "start")
climate_api.register_effect(EFFECT_NAME, handle_effect, "tick")
climate_api.register_effect(EFFECT_NAME, stop_effect, "stop")
climate_api.set_effect_cycle(EFFECT_NAME, climate_api.MEDIUM_CYCLE)

View File

@ -62,6 +62,7 @@ climate_mod.state = dofile(modpath .. "/lib/datastorage.lua")
climate_api = dofile(modpath .. "/lib/api.lua")
climate_api.utility = dofile(modpath .. "/lib/api_utility.lua")
climate_api.skybox = dofile(modpath .. "/lib/skybox_merger.lua")
climate_api.player_physics = dofile(modpath .. "/lib/player_physics.lua")
climate_api.environment = dofile(modpath .. "/lib/environment.lua")
climate_mod.world = dofile(modpath .. "/lib/world.lua")
climate_mod.trigger = dofile(modpath .. "/lib/trigger.lua")

View File

@ -3,9 +3,8 @@ local api = {}
-- define various standard effect cycle lengths
api.SHORT_CYCLE = 0 -- for particles and fast animations (use GSCYCLE)
api.DEFAULT_CYCLE = 0.1 -- for most effect types
api.MEDIUM_CYCLE = 2.0 -- for ressource intensive tasks
api.LONG_CYCLE = 5.0 -- for write operations and skybox changes
api.DEFAULT_CYCLE = 2.0 -- for most effect types
api.LONG_CYCLE = 5.0 -- for ressource intensive tasks or timed effects
-- register new weather presets (like rain)
-- @param name <string> Unique preset name, ideally prefixed

View File

@ -1,5 +1,6 @@
local mod_player_monoids = minetest.get_modpath("player_monoids") ~= nil
local mod_playerphysics = minetest.get_modpath("playerphysics") ~= nil
local mod_pova = minetest.get_modpath("pova") ~= nil
local utility = {}
@ -33,31 +34,5 @@ function utility.normalized_cycle(value)
return math.cos((2 * value + 1) * math.pi) / 2 + 0.5
end
-- override player physics
-- use utility mod if possible to avoid conflict
function utility.add_physics(id, player, effect, value)
if mod_player_monoids then
player_monoids[effect]:add_change(player, value, id)
elseif mod_playerphysics then
playerphysics.add_physics_factor(player, effect, id, value)
else
local override = {}
override[effect] = value
player:set_physics_override(override)
end
end
-- reset player phsysics to normal
function utility.remove_physics(id, player, effect)
if mod_player_monoids then
player_monoids[effect]:del_change(player, id)
elseif mod_playerphysics then
playerphysics.remove_physics_factor(player, effect, id)
else
local override = {}
override[effect] = 1
player:set_physics_override(override)
end
end
return utility

View File

@ -1,13 +1,12 @@
local state = minetest.get_mod_storage()
if not state:contains("noise_timer") then
if not state:contains("heat_random") then
state:from_table({
heat_random = 1,
humidity_random = 1,
humidity_base = 50,
wind_x = 0.5,
wind_z = 0.5,
noise_timer = 0
wind_z = 0.5
})
end

View File

@ -1,18 +1,18 @@
climate_api.register_influence("heat", function(pos)
return climate_api.environment.get_heat(pos)
end)
climate_api.register_influence("heat",
climate_api.environment.get_heat
)
climate_api.register_influence("base_heat", function(pos)
return minetest.get_heat(pos)
end)
climate_api.register_influence("base_heat",
minetest.get_heat
)
climate_api.register_influence("humidity", function(pos)
return climate_api.environment.get_humidity(pos)
end)
climate_api.register_influence("humidity",
climate_api.environment.get_humidity
)
climate_api.register_influence("base_humidity", function(pos)
return minetest.get_humidity(pos)
end)
climate_api.register_influence("base_humidity",
minetest.get_humidity
)
-- see https://en.wikipedia.org/wiki/Dew_point#Simple_approximation
climate_api.register_influence("dewpoint", function(pos)
@ -58,6 +58,6 @@ climate_api.register_influence("daylight", function(pos)
return minetest.env:get_node_light(pos, 0.5)
end)
climate_api.register_global_influence("time", function()
return minetest.get_timeofday()
end)
climate_api.register_global_influence("time",
minetest.get_timeofday()
)

View File

@ -1,5 +1,5 @@
local GSCYCLE = 0.03 * climate_mod.settings.tick_speed -- only process event loop after this time amount
local WORLD_CYCLE = 15.00 * climate_mod.settings.tick_speed -- only update global environment influences after this time amount
local GSCYCLE = 0.03 * climate_mod.settings.tick_speed -- only process event loop after this amount of time
local WORLD_CYCLE = 15.00 * climate_mod.settings.tick_speed -- only update global environment influences after this amount of time
local gs_timer = 0
local world_timer = 0
@ -10,10 +10,8 @@ minetest.register_globalstep(function(dtime)
if gs_timer + dtime < GSCYCLE then return else gs_timer = 0 end
if world_timer >= WORLD_CYCLE then
local noise_timer = climate_mod.state:get_float("noise_timer") + world_timer
world_timer = 0
climate_mod.state:set_float("noise_timer", noise_timer)
climate_mod.world.update_status(noise_timer)
climate_mod.world.update_status(minetest.get_gametime())
climate_mod.global_environment = climate_mod.trigger.get_global_environment()
end

88
lib/player_physics.lua Normal file
View File

@ -0,0 +1,88 @@
local mod_player_monoids = minetest.get_modpath("player_monoids") ~= nil
local mod_playerphysics = minetest.get_modpath("playerphysics") ~= nil
local mod_pova = minetest.get_modpath("pova") ~= nil
local physics = {}
-- use player monoids if available
if mod_player_monoids then
function physics.add(id, player, effect, value)
player_monoids[effect]:add_change(player, value, id)
end
function physics.remove(id, player, effect)
player_monoids[effect]:del_change(player, id)
end
-- fallback to playerphysics if available
elseif mod_playerphysics then
function physics.add(id, player, effect, value)
playerphysics.add_physics_factor(player, effect, id, value)
end
function physics.remove(id, player, effect)
playerphysics.remove_physics_factor(player, effect, id)
end
-- fallback to pova if available
-- pova uses additive effect modifiers
-- this tries to simulate multiplication
-- by including the default value in modifier calculation
elseif mod_pova then
function physics.add(id, player, effect, value)
local playername = player:get_player_name()
local defaults = pova.get_override(playername, "default")
local default
if defaults == nil or defaults[effect] == nil then default = 1
else default = defaults[effect] end
local override = {}
override[effect] = (value * default) - default
pova.add_override(playername, id, override)
pova.do_override(playername)
end
function physics.remove(id, player, effect)
local playername = player:get_player_name()
pova.del_override(playername, id)
pova.do_override(playername)
end
-- fallback to vanilla override as last resort
else
local physics = {}
local function apply_physics(player)
local playername = player:get_player_name()
local override = { speed = 1, jump = 1, gravity = 1 }
for effect, modifiers in pairs(physics[playername]) do
override[effect] = 1
for _, modifier in pairs(modifiers) do
override[effect] = override[effect] * modifier
end
end
player:set_physics_override(override)
end
function physics.add(id, player, effect, value)
local playername = player:get_player_name()
if physics[playername] == nil then physics[playername] = {} end
if physics[playername][effect] == nil then physics[playername][effect] = {} end
physics[playername][effect][id] = value
apply_physics(player)
end
function physics.remove(id, player, effect)
local playername = player:get_player_name()
if physics[playername] == nil then return end
if physics[playername][effect] == nil then return end
if physics[playername][effect][id] == nil then return end
physics[playername][effect][id] = nil
apply_physics(player)
end
minetest.register_on_leaveplayer(function(player)
local playername = player:get_player_name()
physics[playername] = nil
end)
end
return physics

View File

@ -72,7 +72,7 @@ local function set_skybox(playername, sky)
player:set_stars(sky.star_data)
end
function skybox.update_skybox(playername)
function skybox.update(playername)
local p_layers = layers[playername]
local sky = table.copy(default_sky)
if p_layers == nil then p_layers = {} end
@ -91,12 +91,12 @@ function skybox.update_skybox(playername)
set_skybox(playername, sky)
end
function skybox.add_layer(playername, name, sky)
function skybox.add(playername, name, sky)
if layers[playername] == nil then layers[playername] = {} end
layers[playername][name] = sky
end
function skybox.remove_layer(playername, name)
function skybox.remove(playername, name)
if layers[playername] == nil or layers[playername][name] == nil then return end
layers[playername][name] = nil
end

View File

@ -2,7 +2,7 @@ name = climate_api
title = Climate API
author = TestificateMods
release = 2
optional_depends = skylayer, player_monoids, playerphysics
optional_depends = player_monoids, playerphysics, pova
description = """
A powerful engine for weather presets and visual effects.
Use the regional climate to set up different effects for different regions.