diff --git a/api_doc.md b/api_doc.md new file mode 100644 index 0000000..04cefe3 --- /dev/null +++ b/api_doc.md @@ -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 ``: 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 ``: 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 ``: 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
``: 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 ``: 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
`` would have looked like. + +__Returns__: ``nil`` + + +### Register Weather Effect +``climate_api.register_effect(name, handler, htype)`` + +__Parameters__: +- ``name ``: 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 ``: 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 ``: The identifier of a registered weather effect +- ``cycle ``: 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 `` A unique name identifying the registered influence +- ``func ``: 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 `` A unique name identifying the registered influence +- ``func ``: 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 ``: Coordinates of requested location + +__Returns__: ```` indicating current temperature in °F + +### Get Humidity At Position +``climate_api.environment.get_humidity(pos)`` + +__Parameter__: ``pos ``: Coordinates of requested location + +__Returns__: ```` indicating current humidity + +### Get Current Windspeed +``climate_api.environment.get_wind()`` + +__Returns__: ```` 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 ``: A unique name used to identify the modifier. Should be prefixed with the mod's name. +- ``player ``: The player affected by the physics change +- ``effect <"speed" | "jump" | "gravity">``: The type of physics to be changed +- ``value ``: 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 ``: The name used in ``player_physics.add`` that identifies a registered modifier +- ``player ``: 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
``: The base table consisting of default values or other data +- ``b
``: The prioritized table to merge with, and possibly override A + +__Returns__: ``
`` consisting of all attributes from A and B. + +### ``climate_api.utility.sigmoid(value, max, growth, midpoint)`` + +### ``climate_api.utility.normalized_cycle(value)`` diff --git a/ca_effects/damage.lua b/ca_effects/damage.lua index 65245be..e4a27b2 100644 --- a/ca_effects/damage.lua +++ b/ca_effects/damage.lua @@ -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 [1]: The amount of damage to be applied per successful roll. -- chance [1]: Defines a 1/x roll per cycle for the player to get damaged. Higher values result in less frequent damage. +- rarity [1]: Defines a 1/x chance per cycle for the player to get damaged. Higher values result in less frequent damage. - check
[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 [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) \ No newline at end of file diff --git a/ca_effects/hud_overlay.lua b/ca_effects/hud_overlay.lua index e816854..c589637 100644 --- a/ca_effects/hud_overlay.lua +++ b/ca_effects/hud_overlay.lua @@ -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) \ No newline at end of file diff --git a/ca_effects/particles.lua b/ca_effects/particles.lua index ec50a1d..4d8e280 100644 --- a/ca_effects/particles.lua +++ b/ca_effects/particles.lua @@ -10,29 +10,33 @@ Furthermore, the following default values have been changed: - collisiondetection [true] - collision_removal [true] - playername [current player] (Set to empty string to show for everyone) +- vertical [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 [nil] (Overrides both minsize and maxsize if set) +The following optional values have been introduced for convenience: - boxsize [nil] (Overrides minpos and maxpos based on specified sizes per direction with the player in the center) - boxsize [nil] (If set to a number, the resulting vector will have the specified size in all directions) - v_offset [0] (Use in conjunctin with boxsize. Adds specified height to minpos and maxpos y-coordinates) +- attach_to_player [false] (Overrides attached object with current player) + +The following optional values have been expanded with additional value types for convenience: +- size [nil] (Overrides both minsize and maxsize if set) - minvel [nil] (Overrides minvel with a downward facing vector of specified length) - maxvel [nil] (Overrides maxvel with a downward facing vector of specified length) - velocity [nil] (Overrides both minvel and maxvel if set) - minacc [nil] (Overrides minacc with a downward facing vector of specified length) - maxacc [nil] (Overrides maxacc with a downward facing vector of specified length) - acceleration [nil] (Overrides both minacc and maxacc if set) -- attach_to_player [false] (Overrides attached object with current player) The following new behaviours have been introduced: - use_wind [true] (Adjusts velocity and position for current windspeed) -- detached [false] (Unless enabled, considers positions as relative to current player like being attached) -- vertical [nil] (Unless explicitly set, will be automatically set based on direction of velocity) +- detach [false] (Unless enabled, considers positions as relative to current player as if spawner's position would be attached) +- adjust_for_velocity [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 end - config.detached = nil + 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) \ No newline at end of file +climate_api.set_effect_cycle(EFFECT_NAME, CYCLE_LENGTH) \ No newline at end of file diff --git a/ca_effects/skybox.lua b/ca_effects/skybox.lua index 3110270..9ca907f 100644 --- a/ca_effects/skybox.lua +++ b/ca_effects/skybox.lua @@ -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) \ No newline at end of file diff --git a/ca_effects/sound.lua b/ca_effects/sound.lua index 6da671d..3ab8d8b 100644 --- a/ca_effects/sound.lua +++ b/ca_effects/sound.lua @@ -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) \ No newline at end of file diff --git a/init.lua b/init.lua index 6d590b8..636a4dd 100644 --- a/init.lua +++ b/init.lua @@ -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") diff --git a/lib/api.lua b/lib/api.lua index 4cb6b8c..d0223da 100644 --- a/lib/api.lua +++ b/lib/api.lua @@ -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 Unique preset name, ideally prefixed diff --git a/lib/api_utility.lua b/lib/api_utility.lua index b121298..55adccc 100644 --- a/lib/api_utility.lua +++ b/lib/api_utility.lua @@ -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 diff --git a/lib/datastorage.lua b/lib/datastorage.lua index 6d5fdd1..455e857 100644 --- a/lib/datastorage.lua +++ b/lib/datastorage.lua @@ -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 diff --git a/lib/influences.lua b/lib/influences.lua index ec5ae58..a452692 100644 --- a/lib/influences.lua +++ b/lib/influences.lua @@ -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) \ No newline at end of file +climate_api.register_global_influence("time", + minetest.get_timeofday() +) \ No newline at end of file diff --git a/lib/main.lua b/lib/main.lua index 2cc834a..e0228c4 100644 --- a/lib/main.lua +++ b/lib/main.lua @@ -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 diff --git a/lib/player_physics.lua b/lib/player_physics.lua new file mode 100644 index 0000000..0c97cba --- /dev/null +++ b/lib/player_physics.lua @@ -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 \ No newline at end of file diff --git a/lib/skybox_merger.lua b/lib/skybox_merger.lua index d617895..ea10430 100644 --- a/lib/skybox_merger.lua +++ b/lib/skybox_merger.lua @@ -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 diff --git a/mod.conf b/mod.conf index 55cb318..30d1fed 100644 --- a/mod.conf +++ b/mod.conf @@ -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.