code:
# Player Monoids

This is a small library for managing global player state, so that changes made
from different mods do not result in unexpected behavior. The README gives an
introduction to the mod, but you might want to reference API.md along the way.
This mod, combined with playereffects, deprecates monoidal_effects.

Global Player State
===================
Players have behavior-affecting state that can be modified through mods. A couple
examples are physics overrides and armor groups. If more than one mod tries to
change them, it can result in unexpected results.

For example, a player could be
under a speed boost effect from a playereffects effect, and then sleep in a bed.
The bed sets the player's speed to 0, and sets it back to 1 when they get out.
Because the beds mod had no way of knowing that the player was supposed to have
a speed boost, it effectively removed it. One hack to "fix" it would be to save
the player's speed and restore it on wakeup, but this would have its own problems
if the effect wears off in bed. The beds mod would restore the boosted speed,
which wouldn't be removed, since the effect already went away. Thus an exploit
allowing a permanent (until log out) speed boost is introduced.

Player Monoids manages this by creating layers (monoids) on top of player state,
which can keep track of different changes and combine them usefully.

Monoids
=======

Creation
--------
A monoid in Player Monoids is an interface to one piece of player state. For
example, you could have one monoid covering physics overrides, and another
covering fly privilege. You could define a speed monoids like this:
```
-- The values in my speed monoid must be speed multipliers (numbers).
mymod.speed_monoid = player_monoids.make_monoid({
	combine = function(speed1, speed2)
		return speed1 * speed2
	end,
	fold = function(tab)
		local res = 1
	     	for _, speed in pairs(tab) do
			res = res * speed
	     	end
	end,
	identity = 1,
	apply = function(speed, player)
		local override = player:get_physics_override()
		override.speed = speed
		player:set_physics_override(override)
	end,
	on_change = function() return end,
})
```

This says that two speed multipliers can be combined by multiplication, that
1 can be used as a neutral element, and that the "interpretation" of the speed
multiplier is to set the player's speed physics override to that value. It also
says that nothing in particular needs to happen when the speed changes, other
than applying the new speed multiplier.

Use
---
To add or remove change through a monoid, you must use the ```add_change```
and ```del_change``` methods. For example, you could speed the player up
temporarily like this:
```
-- Zoom!
local zoom_id = mymod.speed_monoid:add_change(some_player, 10)

minetest.after(5,function() mymod.speed_monoid:del_change(some_player, zoom_id) end)
```
You could also specify a string ID to use, instead of the numerical one that
is automatically provided:
```
-- Zoom Mk. II
mymod.speed_monoid:add_change(some_player, 10, "mymod:zoom")

minetest.after(5,function() mymod.speed_monoid:del_change(some_player, "mymod:zoom") end)
```

Reading Values
--------------
You can use ```monoid:value(player)``` to read the current value of the monoid,
for that player. This could be useful if it doesn't just represent built-in
player state. For example, it could represent gardening skill, and you might use
it to calculate the chance of success when harvesting spices.

Nesting Monoids
---------------
You may have already noticed one limitation of this design. That is, for each
kind of player state, you can only combine state changes in one way. If the
standard speed monoid combines speed multipliers by multiplication, you cannot
change it to instead choose the highest speed multiplier. Unfortunately, there
is currently no way change this - you will have to hope that the given monoid
combines in a useful way. However, it is possible to manage a subset of the
values in a custom way.

Suppose that a speed monoid (```mymod.speed_monoid```) already exists, using
multiplication, but you want to write a mod with speed boosts, and only apply
the strongest boost. Most of it could be done the same way:
```
-- My speed boosts monoid takes speed multipliers (numbers) that are at least 1.
newmod.speed_boosts = player_monoids.make_monoid({
	combine = function(speed1, speed2)
		return math.max(speed1, speed2)
	end,
	fold = function(tab)
		local res = 1
	     	for _, speed in pairs(tab) do
			res = math.max(res, speed)
	     	end
	end,
	identity = 1,
	apply = ???
	on_change = function() return end,
})
```
But we cannot just change the player speed in ```apply```, otherwise we will
break compatibility with the original speed monoid! The trick here is to use
the original monoid as a proxy for our effects.
```
apply = function(speed, player)
      mymod.speed_monoid:add_change(player, speed, "newmod:speed_boosts")
end
```
This means the speed boosts we control can be limited to the strongest boost, but
the resulting boost will still play nice with speed effects from other mods.
You could even add another "nested monoid" just for speed maluses, that takes
the worst speed drain and applies it as a multiplier.

Standard Monoids
================
In the spirit of compatibility, this mod provides some canonical monoids for
commonly used player state. They combine values in a way that should allow
different mods to affect player state fairly. If you make another monoid handling
the same state as one of these, you will break compatibility with any mods using
the standard monoid.

Physics Overrides
-----------------
These monoids set the multiplier of the override they are named after. All three
take non-negative numbers as values and combine them with multiplication. They
are:
 * ```player_monoids.speed```
 * ```player_monoids.jump```
 * ```player_monoids.gravity```

Privileges
----------
These monoids set privileges that affect the player's ordinary gameplay. They
take booleans as input and combine them with logical or. They are:
 * ```player_monoids.fly```
 * ```player_monoids.noclip```

Other
-----
 * ```player_monoids.collisionbox``` - Sets the player's collisionbox. Values are
 3D multiplier vectors, which are combined with component-wise multiplication.
 * ```player_monoids.visual_size``` - Sets the player's collisionbox. Values are
 2D multiplier vectors (x and y), which are combined with component-wise
 multiplication.

Use with playereffects
======================
Player Monoids does not provide anything special for persistent effects with
limited lifetime. By using monoids with Wuzzy's playereffects, you can easily
create temporary effects that stack with each other. As an example, an effect
that gives the player 2x speed:
```
local speed = player_monoids.speed

local function apply(player)
  speed:add_change(player, 2, "mymod:2x_speed")
end

local function cancel(player)
  speed:del_change(player, "mymod:2x_speed")
end

local groups = { "mymod:2x_speed" }

playereffects.register_effect_type("mymod:2x_speed", "2x Speed", groups, apply, cancel)
```

Note that this effect does NOT use the "speed" effect group. As long as other
speed effects use the speed monoid, we do not want them to be cancelled, since
the goal is to combine the effects together. It does use a singleton group to
prevent multiple instances of the same effect. I think that playereffects require
effects to belong to at least one group, but I am not sure.

Caveats
=======
* If the global state managed by a monoid is modified by something other than
the monoid, you will have the same problem as when two mods both independently
try to modify global state without going through a monoid.
 * This includes playereffects effects that affect global player state without
going through a monoid.
* You will also get problems if you use multiple monoids to manage the same
global state.
* The order that different effects get combined together is based on key order,
which may not be predictable. So you should try to make your monoids commutative
in addition to associative, or at least not care if the order of two changes
is swapped.


code:
-- Copyright (c) raymoo 2016
-- Licensed under Apache 2.0 license. See COPYING for details.

-- Any documentation here are internal details, please avoid using them in your
-- mod.

local modpath = minetest.get_modpath(minetest.get_current_modname()) .. "/"

player_monoids = {}

local mon_meta = {}

mon_meta.__index = mon_meta

local nop = function() end

-- A monoid object is a table with the following fields:
--   def: The monoid definition
--   player_map: A map from player names to their effect tables. Effect tables
--     are maps from effect IDs to values.
--   value_cache: A map from player names to the cached value for the monoid.
--   next_id: The next unique ID to assign an effect.

local function monoid(def)
	local mon = {}

	local actual_def = {}

	for k, v in pairs(def) do
		actual_def[k] = v
	end

	if not actual_def.apply then
		actual_def.apply = nop
	end

	if not actual_def.on_change then
		actual_def.on_change = nop
	end

	mon.def = actual_def

	local p_map = {}
        mon.player_map = p_map

	mon.next_id = 1

	local v_cache = {}
	mon.value_cache = v_cache

	setmetatable(mon, mon_meta)

	minetest.register_on_leaveplayer(function(player)
		local p_name = player:get_player_name()
		p_map[p_name] = nil
		v_cache[p_name] = nil
	end)

	return mon
end

player_monoids.make_monoid = monoid

function mon_meta:add_change(player, value, id)
	local p_name = player:get_player_name()

	local def = self.def

	local p_effects = self.player_map[p_name]
	if p_effects == nil then
		p_effects = {}
		self.player_map[p_name] = p_effects
	end

	local actual_id

	if id then
		actual_id = id
	else
		actual_id = self.next_id
		self.next_id = actual_id + 1
	end

	local old_total = self.value_cache[p_name]
	p_effects[actual_id] = value
	local new_total = def.fold(p_effects)
	self.value_cache[p_name] = new_total

	def.apply(new_total, player)
	def.on_change(old_total, new_total, player)

	return actual_id
end

function mon_meta:del_change(player, id)
	local p_name = player:get_player_name()

	local def = self.def

	local p_effects = self.player_map[p_name]
	if p_effects == nil then return end

	local old_total = self.value_cache[p_name]
	p_effects[id] = nil
	local new_total = def.fold(p_effects)
	self.value_cache[p_name] = new_total

	def.apply(new_total, player)
	def.on_change(old_total, new_total, player)
end

function mon_meta:value(player)
	local p_name = player:get_player_name()
	return self.value_cache[p_name] or self.def.identity
end

dofile(modpath .. "standard_monoids.lua")
dofile(modpath .. "test.lua")

repo_name: korkut/MeseCraft

license: mit


code:
name = player_monoids
description = """
A library for managing global player state,
such as physics overrides or player visual size.
"""


code:
-- Standard effect monoids, to provide canonicity.

local function mult(x, y) return x * y end

local function mult_fold(elems)
	local tot = 1

	for _, v in pairs(elems) do
		tot = tot * v
	end

	return tot
end

local function v_mult(v1, v2)
	local res = {}

	for k, v in pairs(v1) do
		res[k] = v * v2[k]
	end

	return res
end

local function v_mult_fold(identity)
	return function(elems)
		local tot = identity

		for _, v in pairs(elems) do
			tot = v_mult(tot, v)
		end

		return tot
	end
end

local monoid = player_monoids.make_monoid

-- Speed monoid. Effect values are speed multipliers. Must be nonnegative
-- numbers.
player_monoids.speed = monoid({
	combine = mult,
	fold = mult_fold,
	identity = 1,
	apply = function(multiplier, player)
		local ov = player:get_physics_override()
		ov.speed = multiplier
		player:set_physics_override(ov)
	end,
})

-- Jump monoid. Effect values are jump multipliers. Must be nonnegative
-- numbers.
player_monoids.jump = monoid({
	combine = mult,
	fold = mult_fold,
	identity = 1,
	apply = function(multiplier, player)
		local ov = player:get_physics_override()
		ov.jump = multiplier
		player:set_physics_override(ov)
	end,
})

-- Gravity monoid. Effect values are gravity multipliers.
player_monoids.gravity = monoid({
	combine = mult,
	fold = mult_fold,
	identity = 1,
	apply = function(multiplier, player)
		local ov = player:get_physics_override()
		ov.gravity = multiplier
		player:set_physics_override(ov)
	end,
})

-- Fly ability monoid. The values are booleans, which are combined by or. A true
-- value indicates having the ability to fly.
player_monoids.fly = monoid({
	combine = function(p, q) return p or q end,
	fold = function(elems)
		for _, v in pairs(elems) do
			if v then return true end
		end

		return false
	end,
	identity = false,
	apply = function(can_fly, player)
		local p_name = player:get_player_name()
		local privs = minetest.get_player_privs(p_name)

		if can_fly then
			privs.fly = true
		else
			privs.fly = nil
		end

		minetest.set_player_privs(p_name, privs)

	end,
})

-- Noclip ability monoid. Works the same as fly monoid.
player_monoids.noclip = monoid({
	combine = function(p, q) return p or q end,
	fold = function(elems)
		for _, v in pairs(elems) do
			if v then return true end
		end

		return false
	end,
	identity = false,
	apply = function(can_noclip, player)
		local p_name = player:get_player_name()
		local privs = minetest.get_player_privs(p_name)

		if can_noclip then
			privs.noclip = true
		else
			privs.noclip = nil
		end

		minetest.set_player_privs(p_name, privs)

	end,
})

local def_col_scale = { x=0.3, y=1, z=0.3 }

-- Collisionbox scaling factor. Values are a vector of x, y, z multipliers.
player_monoids.collisionbox = monoid({
	combine = v_mult,
	fold = v_mult_fold({x=1, y=1, z=1}),
	identity = {x=1, y=1, z=1},
	apply = function(multiplier, player)
		local v = vector.multiply(def_col_scale, multiplier)

		player:set_properties({
			collisionbox = { -v.x, -v.y, -v.z, v.z, v.y, v.z }
		})
	end,
})

player_monoids.visual_size = monoid({
	combine = v_mult,
	fold = v_mult_fold({x=1, y=1}),
	identity = {x=1, y=1},
	apply = function(multiplier, player)
		player:set_properties({
			visual_size = multiplier
		})
	end,
})


code:
local speed = player_monoids.speed

minetest.register_privilege("monoid_master", {
	description = "Allows testing of player monoids.",
	give_to_singleplayer = false,
	give_to_admin = true,
})

local function test(player)
	local ch_id = speed:add_change(player, 10)
	local p_name = player:get_player_name()

	minetest.chat_send_player(p_name, "Your speed is: " .. speed:value(player))

	minetest.after(3, function()
		speed:del_change(player, ch_id)
		minetest.chat_send_player(p_name, "Your speed is: " .. speed:value(player))
	end)
end

minetest.register_chatcommand("test_monoids", {
	description = "Runs a test on monoids",
	privs = { monoid_master = true },
	func = function(p_name)
		test(minetest.get_player_by_name(p_name))
	end,
})


code:
# Player Effects
## Summary
This is an framework for assigning temporary status effects to players. This mod is aimed to modders and maybe interested people.

## Profile
* Name: Player Effects
* Short name: `playereffects`
* Current version: 1.2.0 (This is a [SemVer](http://semver.org/).)
* Dependencies: None!
* License of everything: MIT License
* Discussion page: [here](https://forum.minetest.net/viewtopic.php?f=11&t=9689)

## Information for players
This mod alone is not aimed directly at players. Briefly, the point of this mod is to help other mods to implement temporary status effects for players in a clean and consistant way.
Here is the information which may be relevant to you: Your current status effects are shown on the HUD on the right side, along with a timer which shows the time until the effect gets disabled. It is possible for the server to disable this feature entirely. Some status effects may also be hidden and are never exposed to the HUD.

You only have to install this mod iff you have a mod which implements Player Effects. Here is a list of known mods which do:

* Magic Beans—Wuzzy’s Fork [`magicbeans_w`]

## Information for server operators
By default, this mod stores the effects into the file `playereffects.mt` in the current world path every 10 seconds. On a regular server shutdown, this file is also written to. The data in this file is read when the mod is started.

It is save to delete `playereffects.mt` when the mod does currently not run. This simply erases all active and inactive effects when the server starts again.

You can disable the automatic saving in `settings.lua`.

### Configuration
Player Effects can be configured. Just edit the file `settings.lua`. You find everything you need to know in that file. Be careful to not delete the lines, just edit them.

## Information for modders
This is a framework for other mods to depend on. It provides means to define, apply and remove temporary status effects to players in a (hopefully) unobstrusive way.
A status effect is an effect for a player which changes some property of the player. This property can be practically everything. Currently, the framework supports one kind of effect, which I call “exclusive effects”. For each property (speed, gravity, whatver), there can be only one effect in place at the same time. Here are some examples for possible status effects:

* high walking speed (`speed` property)
* high jump height (`jump` property)
* low player gravity (`gravity` property)
* high player gravity (`gravity` property)
* having the X privilege granted (binary “do I have the property?” property) (yes, this is weird, but it works!)

The framework aims to provide means to define effect types and to apply and cancel effects to players. The framework aims to be a stable foundation stone. This means it needs a lot of testing.

## Known bugs
### Effect timers don’t stop when game gets paused
When you paused the game in singleplayer mode, the effect timers just continue as if nothing happened. Of course, all effect timers should be stopped while the game is paused, like everything else. Apparently this bug cannot be fixed with the current Lua API.

## API documentation
### Data types
#### Effect type (`effect_type`)
An effect type is a description of what is later to be concretely applied as an effect to a player. An effect type, however, is *not* assigned to a player. There are two kinds of effect types: Repeating and non-repeating. See the section on `effect` for more information.

`effect_type` is a table with these fields:

* `description`: Human-readable short description of the effect. Will be exposed to the HUD, iff `hidden` is `false`.
* `groups`: A table of groups to which this effect type belongs to.
* `apply`: Function to be called when effect is applied. See `playereffects.register_effect_type`.
* `cancel`: Function to be called when effect is cancelled. See `playereffects.register_effect_type`.
* `icon`: This is optional. It can be the file name of a texture. Should have a size of 16px×16px. Will be exposed to the HUD, iff `hidden` is `false`.
* `hidden`: Iff this is false, it will not be exposed to the HUD when this effect is active.
* `cancel_on_death`: Iff this is true, the effect will be cancelled automatically when the player dies.
* `repeat_interval` is an optional number. When specified, the effects of this type becomes a repeating effect. Repeating effects call `apply` an arbitrary number of times; non-repeating effects just call it once when the effect is created. The number specifies the interval in seconds between each call. Iff this parameter is `nil`, the effect type is a non-repeating effect.

Normally you don’t need to read or edit fields of this table. Use `playereffects.register_effect_type` to add a new effect type to Player Effects.

#### Effect group
An effect group is basically a concept. Any effect type can be member of any number of effect groups. The main point of effect groups is to find effects which affect the same property. For example, an effect which makes you faster and another effect which makes you slower both affect the same property: speed. The group for that then would be the string `"speed"`. See also `examples.lua`, which includes the effects `high_speed` and `low_speed`.

Currently, the main rule of Player Effects requires that there can only be one effect in place. Player Effects already does that job for you. Back to the example: it is possible to be fast and it is possible to be slow. But it is not possible to be fast *and* slow at the same time. Player Effects ensures that by cancelling all conflicting concepts before applying a new one.

The concept of groups may be changed or extended in the future.

The following effect group names have standardized meanings and should solely be used for their intended purpose:

* `speed`: Affects the player speed set by the `speed` value of `set_physics_override`
* `gravity`: Affects the player gravity set by the `gravity` value of `set_physics_override`
* `jump`: Affects the player jump strength set by the `jump` value of `set_physics_override`
* `health`: Affects the player health
* `breath`: Affects the player breath

You can also invent effect groups (like the groups in Minetest) on the fly. A group is just a string. Practically, you should use groups which other people use.

#### Effect (`effect`)
An effect is an current change of a player property (like speed, jump height, and so on). It is the realization of an effect type. All effects are temporary. There are currently two types of effects: Repeating and non-repeating. Non-repeating effects call their `apply` callback once when they are created. Repeating effects call their apply callback multiple times with a specified interval. By default, effects are non-repeating.

`effect` is a table with the following modding-relevant fields:

* `playername`: The name of the player to which the effect belongs to.
* `effect_id`: A globally unique identifier of the effect. It is a number and assigned automatically by Player Effects.
* `effect_type_id`: The identifier of the effect’s effect type. It is a string and assigned by `playereffects.register_effect_type`.
* `metadata`: An optional field which may contain a table with additional, modder-defined data to be “remembered” for later. The `apply` callback can set this field.

Internally, Player Effects also uses these fields:

* `start_time`: The operating system time (from `os.time()`) of when the effect has been started.
* `time_left`: The number of seconds left before the effect runs out. This number is only set when the effect starts or the effect is unfrozen because i.e. a player re-joins. You can’t use this field to blindly get the remaining time of the effect.
* `repeat_interval_start_time` and `repeat_interval_time_left`: Same as `start_time` and `time_left`, but for repeating effects.

You should normally not need to care about these internally used fields.

### Functions
#### `playereffects.register_effect_type(effect_type_id, description, icon, groups, apply, cancel, hidden, cancel_on_death, repeat_interval)`
Adds a new effect type for the Player Effects mods, so it can be later be applied to players.

##### Parameters
* `effect_type_id` is the identifier (a string) which is internally used by the mod. Later known as `effect_type_id`. You may choose the identifier at will, but please use only alphanumeric ASCII characters. The identifier must be unique along all mods.
* `description` is the text which is exposed to the HUD and visible to the player.
* `icon`: This is optional an can be `nil`. It can be the file name of a texture. Should have a size of 16px×16px. In this case, this is the icon for the HUD. Basically this is just eye-candy. If this is `nil`, no icon is shown. The icon will be exposed to the HUD, iff `hidden` is `false`.
* `groups` is a table of strings to which the effect type is assigned to.
* `apply`: See below.
* `cancel`: See below.
* `hidden` is an optional boolean value. Iff `true`, the effect description and icon will not be exposed to the player HUD. Otherwise, the effect is exposed. Default: `false`
* `cancel_on_death` is an optional boolean value. Iff true, the effect will be cancelled automatically when the player dies. Default: `true`.
* `repeat_interval` is an optional number. When specified, the effects of this type becomes a repeating effect. Repeating effects call `apply` an arbitrary number of times; non-repeating effects just call it once when the effect is created. The number specifies the interval in seconds between each call. Iff this parameter is `nil`, the effect type is a non-repeating effect.

###### `apply` function
The `apply` function is a callback function which is called by Player Effects. Here the modder can put all the gameplay-relevant code.

`apply` takes a player object as its only argument. This is the player to which the effect is applied to.

The function may return a table. This table will be added as the field `metadata` to the resulting `effect`.

The function may return `false`. This is used to tell Player Effects that the effect could, for whatever reason, not be successfully applied. Currently, this feature is experimental and possibly broken.

The function may also return just `nil` on a normal success without metadata.

###### `cancel` function
The `cancel` function is called by Player Effects when the effect is to be cancelled. Here the modder can do all the code which is needed to revert the changes an earlier `apply` call made.

`cancel` takes an `effect` as its first argument and a player object as its second argument. Remember, this `effect` may also contain a field called `metadata`, which may have been added by an earlier `apply` call. `player` is the player to which the effect is/was applied. This argument is just there for convenience reasons.

Player Effects does not care about the return value of this function.

##### Return value
Always `nil`.

#### `playereffects.apply_effect_type(effect_type_id, duration, player, repeat_interval_time_left)`
Attempts to apply a new effect of a certain type for a certain duration to a certain player. This function can fail, although this should rarely happen. This function handles non-repeating effects and repeating effects as well.

##### Parameters
* `effect_type_id`: The identifier of the effect type. This is the name which was used in `playereffects.register_effect_type` and always a string.
* `duration`: How long the effect. Please use only positive values and only integers. If a repeating effect type is specified, this number specifies the number of repetitions; for non-repeating effects this number specifies the effect duration in seconds.
* `player`: The player object to which the new effect should be applied to.
* `repeat_interval_time_left`: This parameter is optional and only for repeating effects. If it is a number, it specifies the time until the first call of the `apply` callback fires. By default, a full repeat interval is waited until the first call.

##### Return value
The function either returns `false` or a number. Iff the function returns `false`, the effect was not successfully applied. The function may return `false` on these occasions:

* `player` is not a valid player object
* The `apply` function of the effect type returned `false`

On success, the function returns a number. This number is the effect ID of the effect which has been just created. This effect ID can be used later, for `playereffects.cancel_effect`, for example.

#### `playereffects.cancel_effect(effect_id)`
Cancels a single effect.

##### Parameter
* `effect_id`: The effect ID of the effect which shall be cancelled.

##### Return value
Always `nil`.

#### `playereffects.cancel_effect_group(groupname, playername)`
Cancels all a player’s effects which belong to a certain group.

##### Parameters
* `groupname`: The name of the effect group (string) of which all active effects of the player shall be cancelled.
* `playername`: The name of the player to which the effects which are about to be cancelled belong to.

##### Return value
Always `nil`.

#### `playereffects.cancel_effect_type(effect_type_id, cancel_all, playername)`
Cancels one or all player effect with a certain effect type
Careful! This function has *not* been tested yet!

##### Parameters
* `effect_type_id`: Identifier of the effect type.
* `cancel_all`: Iff true, cancels all active effects with this effect type
* `playername`: Name of the player to which the effects belong to

##### Return value
Always `nil`.

#### `playereffects.get_remaining_effect_time(effect_id)`
Returns the remaining time of an effect.

##### Parameter
* `effect_id`: The effect identifier of the effect in question

##### Return value
Iff the effect exists, the remaining effect time is returned in full seconds. Iff the effect does not exist, `nil` is returned.

#### `playereffects.get_player_effects(playername)`
Returns all active effects of a player.

##### Parameter
`playername`: The name of the player from which the effects are requested.

##### Return value
A table of all `effect`s which belong to the player. If the player does not exist, this function returns an empty table.

#### `playereffects.has_effect_type(playername, effect_type_id)`
Returns `true` iff the provided player has an effect of the specified effect type, `false` otherwise.

##### Parameters
* `playername`: Name of the player to check the existance of the effect type for
* `effect_type_id`: Identifier of the effect type.

## Examples
This mod comes with extensive examples. The examples are disabled by default. Edit `settings.lua` to enable the examples. See `examples.lua` to find out how they are programmed. The examples are only for demonstration purposes. They are not intended to be used in an actual game.

### Chat commands
The examples are mainly accessible with chat commands. Since this is just an example, everyone can use these examples.

#### Apply effect
These commands apply (or try to) apply an effect to you. You will get a response in the chat which give you the `effect_id` on success. On failure, the example will tell you that it failed.

* `fast`: Makes you faster for 10 seconds.
* `slow`: Makes you slower for 120s.
* `hfast`: Makes you faster for 10s. This is a hidden effect and is not exposed to the HUD.
* `highjump`: Increases your jump height for 20s.
* `fly`: Gives you the `fly` privilege for a minute. You keep the privilege even when you die. Better don’t mess around with this privilege manually when you use this.
* `regen`: Gives you a half heart per second 10 times (5 hearts overall healing). This is an example of a repeating effect.
* `slowregen`: Gives you a half heart every 15 seconds, 10 times (5 hearts overall healing). This is an example of a repeating effect.
* `blind`: Tints the whole screen black for 5 seconds. This is highly experimental and will be drawn over many existing HUD elements. In other words, prepare your HUD to be messed up.
* `null`: Tries to apply an effect which always fails. This demonstrates the failure of effects.

#### Cancel effects
* `cancelall`: Cancels all your active effects.

#### Testing
* `stresstest [number]`: Applies `number` dummy effects which don’t do anything to you. Iff omitted, `number` is assumed to be 100. This command is there to test the performance of this mod for absurdly large effect numbers.


code:
----- EXAMPLE EFFECT TYPES -----
--[[ This is just a helper function to inform the user of the chat command
of the result and, if successful, shows the effect ID. ]]
local function notify(name, retcode)
	if(retcode == false) then
		minetest.chat_send_player(name, "Effect application failed. Effect was NOT applied.")
	else
		minetest.chat_send_player(name, "Effect applied. Effect ID: "..tostring(retcode))
	end
end

--[[ Null effect. The apply function always returns false, which means applying the
effect will never succeed ]]
playereffects.register_effect_type("null", "No effect", nil, {},
	function()
		return false
	end
)

-- Makes the player screen black for 5 seconds (very experimental!)
playereffects.register_effect_type("blind", "Blind", nil, {},
	function(player)
		local hudid = player:hud_add({
			hud_elem_type = "image",
			position = { x=0.5, y=0.5 },
			scale = { x=-100, y=-100 },
			text = "playereffects_example_black.png",
		})
		if(hudid ~= nil) then
			return { hudid = hudid }
		else
			minetest.log("error", "[playereffects] [examples] The effect \"Blind\" could not be applied. The call to hud_add(...) failed.")
			return false
		end
	end,
	function(effect, player)
		player:hud_remove(effect.metadata.hudid)
	end
)

-- Makes the user faster
playereffects.register_effect_type("high_speed", "High speed", nil, {"speed"},
	function(player)
		player:set_physics_override(4,nil,nil)
	end,

	function(effect, player)
		player:set_physics_override(1,nil,nil)
	end
)

-- Makes the user faster (hidden effect)
playereffects.register_effect_type("high_speed_hidden", "High speed", nil, {"speed"},
	function(player)
		player:set_physics_override(4,nil,nil)
	end,

	function(effect, player)
		player:set_physics_override(1,nil,nil)
	end,
	true
)

-- Slows the user down
playereffects.register_effect_type("low_speed", "Low speed", nil, {"speed"},
	function(player)
		player:set_physics_override(0.25,nil,nil)
	end,

	function(effect, player)
		player:set_physics_override(1,nil,nil)
	end
)

-- Increases the jump height
playereffects.register_effect_type("highjump", "Greater jump height", "playereffects_example_highjump.png", {"jump"},
	function(player)
		player:set_physics_override(nil,2,nil)
	end,
	function(effect, player)
		player:set_physics_override(nil,1,nil)
	end
)

-- Adds the “fly” privilege. Keep the privilege even if the player dies
playereffects.register_effect_type("fly", "Fly mode available", "playereffects_example_fly.png", {"fly"},
	function(player)
		local playername = player:get_player_name()
		local privs = minetest.get_player_privs(playername)
		privs.fly = true
		minetest.set_player_privs(playername, privs)
	end,
	function(effect, player)
		local privs = minetest.get_player_privs(effect.playername)
		privs.fly = nil
		minetest.set_player_privs(effect.playername, privs)
	end,
	false, -- not hidden
	false  -- do NOT cancel the effect on death
)

-- Repeating effect type: Adds 1 HP per second
playereffects.register_effect_type("regen", "Regeneration", "heart.png", {"health"},
	function(player)
		player:set_hp(player:get_hp()+1)
	end,
	nil, nil, nil, 1
)

-- Repeating effect type: Adds 1 HP per 3 seconds
playereffects.register_effect_type("slowregen", "Slow Regeneration", "heart.png", {"health"},
	function(player)
		player:set_hp(player:get_hp()+1)
	end,
	nil, nil, nil, 3
)

-- Dummy effect for the stress test
playereffects.register_effect_type("stress", "Stress Test Effect", nil, {},
	function(player)
	end,
	function(effect, player)
	end
)

------ Chat commands for the example effects ------
-- Null effect (never succeeds)
minetest.register_chatcommand("null", {
	params = "",
	description = "Does nothing.",
	privs = {},
	func = function(name, param)
		local ret = playereffects.apply_effect_type("null", 5, minetest.get_player_by_name(name))
		notify(name, ret)
	end,
})

minetest.register_chatcommand("blind", {
	params = "",
	description = "Makes your screen black for a short time.",
	privs = {},
	func = function(name, param)
		local ret = playereffects.apply_effect_type("blind", 5, minetest.get_player_by_name(name))
		notify(name, ret)
	end,
})
minetest.register_chatcommand("fast", {
	params = "",
	description = "Makes you fast for a short time.",
	privs = {},
	func = function(name, param)
		local ret = playereffects.apply_effect_type("high_speed", 10, minetest.get_player_by_name(name))
		notify(name, ret)
	end,
})
minetest.register_chatcommand("hfast", {
	params = "",
	description = "Makes you fast for a short time (hidden effect).",
	privs = {},
	func = function(name, param)
		local ret = playereffects.apply_effect_type("high_speed_hidden", 10, minetest.get_player_by_name(name))
		notify(name, ret)
	end,
})
minetest.register_chatcommand("slow", {
	params = "",
	description = "Makes you slow for a long time.",
	privs = {},
	func = function(name, param)
		local ret = playereffects.apply_effect_type("low_speed", 120, minetest.get_player_by_name(name))
		notify(name, ret)
	end,
})
minetest.register_chatcommand("highjump", {
	params = "",
	description = "Makes you jump higher for a short time.",
	privs = {},
	func = function(name, param)
		local ret = playereffects.apply_effect_type("highjump", 20, minetest.get_player_by_name(name))
		notify(name, ret)
	end,
})

minetest.register_chatcommand("fly", {
	params = "",
	description = "Grants you the fly privilege for a minute. You keep the effect when you die.",
	privs = {},
	func = function(name, param)
		local ret = playereffects.apply_effect_type("fly", 60, minetest.get_player_by_name(name))
		notify(name, ret)
	end,
})

minetest.register_chatcommand("regen", {
	params = "",
	description = "Gives you 1 half heart per second 10 times, healing you by 5 hearts in total.",
	privs = {},
	func = function(name, param)
		local ret = playereffects.apply_effect_type("regen", 10, minetest.get_player_by_name(name))
		notify(name, ret)
	end,
})

minetest.register_chatcommand("slowregen", {
	params = "",
	description = "Gives you 1 half heart every 3 seconds 10 times, healing you by 5 hearts in total.",
	privs = {},
	func = function(name, param)
		local ret = playereffects.apply_effect_type("slowregen", 10, minetest.get_player_by_name(name))
		notify(name, ret)
	end,
})

--[[
	Cancel all active effects
]]
minetest.register_chatcommand("cancelall", {
	params = "",
	description = "Cancels all your effects.",
	privs = {},
	func = function(name, param)
		local effects = playereffects.get_player_effects(name)
		for e=1, #effects do
			playereffects.cancel_effect(effects[e].effect_id)
		end
		minetest.chat_send_player(name, "All effects cancelled.")
	end,
})

--[[ The stress test applies a shitload of effects at once.
This is used to test the performance of this mod at very large effect numbers. ]]
minetest.register_chatcommand("stresstest", {
	params = "[<effects>]",
	descriptions = "Start the stress test for Player Effects with <effects> effects.",
	privs = {server=true},
	func = function(name, param)
		local player = minetest.get_player_by_name(name)
		local max = 100
		if(type(param)=="string") then
			if(type(tonumber(param)) == "number") then
				max = tonumber(param)
			end
		end
		minetest.debug("[playereffects] Stress test started for "..name.." with "..max.." effects.")
		for i=1,max do
			playereffects.apply_effect_type("stress", math.random(6,60), player)
			if(i%100==0) then
				minetest.debug("[playereffects] Effect "..i.." of "..max.." applied.")
				minetest.chat_send_player(name, "[playereffects] Effect "..i.." of "..max.." applied.")

			end
		end
	end
})


code:
--[=[ Main tables ]=]

playereffects = {}

--[[ table containing the groups (experimental) ]]
playereffects.groups = {}

--[[ table containing all the HUD info tables, indexed by player names.
A single HUD info table is formatted like this: { text_id = 1, icon_id=2, pos = 0 }
Where:	text_id: HUD ID of the textual effect description
	icon_id: HUD ID of the effect icon (optional)
	pos: Y offset factor (starts with 0)
Example of full table:
{ ["player1"] = {{ text_id = 1, icon_id=4, pos = 0 }}, ["player2] = { { text_id = 5, icon_id=6, pos = 0 }, { text_id = 7, icon_id=8, pos = 1 } } }
]]
playereffects.hudinfos = {}

--[[ table containing all the effect types ]]
playereffects.effect_types = {}

--[[ table containing all the active effects ]]
playereffects.effects = {}

--[[ table containing all the inactive effects.
Effects become inactive if a player leaves an become active again if they join again. ]]
playereffects.inactive_effects = {}

-- Variable for counting the effect_id
playereffects.last_effect_id = 0

--[=[ Include settings ]=]
dofile(minetest.get_modpath("playereffects").."/settings.lua")

-- defaults
if(playereffects.use_hud == nil) then
	playereffects.use_hud = true
end
if(playereffects.use_autosave == nil) then
	playereffects.use_autosave = true
end
if(playereffects.autosave_time == nil) then
	playereffects.autosave_time = 10
end
if(playereffects.use_examples == nil) then
	playereffects.use_examples = false
end

--[=[ Load inactive_effects and last_effect_id from playereffects.mt, if this file exists  ]=]
do
	local filepath = minetest.get_worldpath().."/playereffects.mt"
	local file = io.open(filepath, "r")
	if file then
--		minetest.log("action", "[playereffects] playereffects.mt opened.")
		local string = file:read()
		io.close(file)
		if(string ~= nil) then
			local savetable = minetest.deserialize(string)
			playereffects.inactive_effects = savetable.inactive_effects
--			minetest.debug("[playereffects] playereffects.mt successfully read.")
--			minetest.debug("[playereffects] inactive_effects = "..dump(playereffects.inactive_effects))
			playereffects.last_effect_id = savetable.last_effect_id
--			minetest.debug("[playereffects] last_effect_id = "..dump(playereffects.last_effect_id))

		end
	end
end

function playereffects.next_effect_id()
	playereffects.last_effect_id = playereffects.last_effect_id + 1
	return playereffects.last_effect_id
end

--[=[ API functions ]=]
function playereffects.register_effect_type(effect_type_id, description, icon, groups, apply, cancel, hidden, cancel_on_death, repeat_interval)
	local effect_type = {}
	effect_type.description = description
	effect_type.apply = apply
	effect_type.groups = groups
	effect_type.icon = icon
	if cancel ~= nil then
		effect_type.cancel = cancel
	else
		effect_type.cancel = function() end
	end
	if hidden ~= nil then
		effect_type.hidden = hidden
	else
		effect_type.hidden = false
	end
	if cancel_on_death ~= nil then
		effect_type.cancel_on_death = cancel_on_death
	else
		effect_type.cancel_on_death = true
	end
	effect_type.repeat_interval = repeat_interval

	playereffects.effect_types[effect_type_id] = effect_type
end

function playereffects.apply_effect_type(effect_type_id, duration, player, repeat_interval_time_left)
	local start_time = os.time()
	local is_player = false
	if(type(player)=="userdata") then
		if(player.is_player ~= nil) then
			if(player:is_player() == true) then
				is_player = true
			end
		end
	end
	if(is_player == false) then
		minetest.log("error", "[playereffects] Attempted to apply effect type "..effect_type_id.." to a non-player!")
		return false
	end

	local playername = player:get_player_name()
	local groups = playereffects.effect_types[effect_type_id].groups
	for k,v in pairs(groups) do
		playereffects.cancel_effect_group(v, playername)
	end

	local metadata
	if(playereffects.effect_types[effect_type_id].repeat_interval == nil) then
		local status = playereffects.effect_types[effect_type_id].apply(player)
		if(status == false) then
			minetest.log("action", "[playereffects] Attempt to apply effect type "..effect_type_id.." to player "..playername.." failed!")
			return false
		else
			metadata = status
		end
	end

	local effect_id = playereffects.next_effect_id()
	local smallest_hudpos
	local biggest_hudpos = -1
	local free_hudpos
	if(playereffects.hudinfos[playername] == nil) then
		playereffects.hudinfos[playername] = {}
	end
	local hudinfos = playereffects.hudinfos[playername]
	for effect_id, hudinfo in pairs(hudinfos) do
		local hudpos = hudinfo.pos
		if(hudpos > biggest_hudpos) then
			biggest_hudpos = hudpos
		end
		if(smallest_hudpos == nil) then
			smallest_hudpos = hudpos
		elseif(hudpos < smallest_hudpos) then
			smallest_hudpos = hudpos
		end
	end
	if(smallest_hudpos == nil) then
		free_hudpos = 0
	elseif(smallest_hudpos >= 0) then
		free_hudpos = smallest_hudpos - 1
	else
		free_hudpos = biggest_hudpos + 1
	end

	local repeat_interval = playereffects.effect_types[effect_type_id].repeat_interval
	if(repeat_interval ~= nil) then
		if(repeat_interval_time_left == nil) then
			repeat_interval_time_left = repeat_interval
		end
	end

	--[[ show no more than 20 effects on the screen, so that hud_update does not need to be called so often ]]
	local text_id, icon_id
	if(free_hudpos <= 20) then
		text_id, icon_id = playereffects.hud_effect(effect_type_id, player, free_hudpos, duration, repeat_interval_time_left)
		local hudinfo = {
				text_id = text_id,
				icon_id = icon_id,
				pos = free_hudpos,
		}
		playereffects.hudinfos[playername][effect_id] = hudinfo
	else
		text_id, icon_id = nil, nil
	end

	local effect = {
			playername = playername,
			effect_id = effect_id,
			effect_type_id = effect_type_id,
			start_time = start_time,
			repeat_interval_start_time = start_time,
			time_left = duration,
			repeat_interval_time_left = repeat_interval_time_left,
			metadata = metadata,
	}

	playereffects.effects[effect_id] = effect

	if(repeat_interval ~= nil) then
		minetest.after(repeat_interval_time_left, playereffects.repeater, effect_id, duration, player, playereffects.effect_types[effect_type_id].apply)
	else
		minetest.after(duration, function(effect_id) playereffects.cancel_effect(effect_id) end, effect_id)
	end

	return effect_id
end

function playereffects.repeater(effect_id, repetitions, player, apply)
	local effect = playereffects.effects[effect_id]
	if(effect ~= nil) then
		local repetitions = effect.time_left
		apply(player)
		repetitions = repetitions - 1
		effect.time_left = repetitions
		if(repetitions <= 0) then
			playereffects.cancel_effect(effect_id)
		else
			local repeat_interval = playereffects.effect_types[effect.effect_type_id].repeat_interval
			effect.repeat_interval_time_left = repeat_interval
			effect.repeat_interval_start_time = os.time()
			minetest.after(
				repeat_interval,
				playereffects.repeater,
				effect_id,
				repetitions,
				player,
				apply
			)
		end
	end
end

function playereffects.cancel_effect_type(effect_type_id, cancel_all, playername)
	local effects = playereffects.get_player_effects(playername)
	if(cancel_all==nil) then cancel_all = false end
	for e=1, #effects do
		if(effects[e].effect_type_id == effect_type_id) then
			playereffects.cancel_effect(effects[e].effect_id)
			if(cancel_all==false) then
				return
			end
		end
	end
end

function playereffects.cancel_effect_group(groupname, playername)
	local effects = playereffects.get_player_effects(playername)
	for e=1,#effects do
		local effect = effects[e]
		local thesegroups = playereffects.effect_types[effect.effect_type_id].groups
		local delete = false
		for g=1,#thesegroups do
			if(thesegroups[g] == groupname) then
				playereffects.cancel_effect(effect.effect_id)
				break
			end
		end
	end
end

function playereffects.get_remaining_effect_time(effect_id)
	local now = os.time()
	local effect = playereffects.effects[effect_id]
	if(effect ~= nil) then
		return (effect.time_left - os.difftime(now, effect.start_time))
	else
		return nil
	end
end

function playereffects.cancel_effect(effect_id)
	local effect = playereffects.effects[effect_id]
	if(effect ~= nil) then
		local player = minetest.get_player_by_name(effect.playername)
		local hudinfo = playereffects.hudinfos[effect.playername][effect_id]
		if(hudinfo ~= nil) then
			if(hudinfo.text_id~=nil) then
				player:hud_remove(hudinfo.text_id)
			end
			if(hudinfo.icon_id~=nil) then
				player:hud_remove(hudinfo.icon_id)
			end
			playereffects.hudinfos[effect.playername][effect_id] = nil
		end
		playereffects.effect_types[effect.effect_type_id].cancel(effect, player)
		playereffects.effects[effect_id] = nil
	end
end

function playereffects.get_player_effects(playername)
	if(minetest.get_player_by_name(playername) ~= nil) then
		local effects = {}
		for k,v in pairs(playereffects.effects) do
			if(v.playername == playername) then
				table.insert(effects, v)
			end
		end
		return effects
	else
		return {}
	end
end

function playereffects.has_effect_type(playername, effect_type_id)
	local pe = playereffects.get_player_effects(playername)
	for i=1,#pe do
		if pe[i].effect_type_id == effect_type_id then
			return true
		end
	end
	return false
end

--[=[ Saving all data to file ]=]
function playereffects.save_to_file()
	local save_time = os.time()
	local savetable = {}
	local inactive_effects = {}
	for id,effecttable in pairs(playereffects.inactive_effects) do
		local playername = id
		if(inactive_effects[playername] == nil) then
			inactive_effects[playername] = {}
		end
		for i=1,#effecttable do
			table.insert(inactive_effects[playername], effecttable[i])
		end
	end
	for id,effect in pairs(playereffects.effects) do
		local new_duration, new_repeat_duration
		if(playereffects.effect_types[effect.effect_type_id].repeat_interval ~= nil) then
			new_duration = effect.time_left
			new_repeat_duration = effect.repeat_interval_time_left - os.difftime(save_time, effect.repeat_interval_start_time)
		else
			new_duration = effect.time_left - os.difftime(save_time, effect.start_time)
		end
		local new_effect = {
			effect_id = effect.effect_id,
			effect_type_id = effect.effect_type_id,
			time_left = new_duration,
			repeat_interval_time_left = new_repeat_duration,
			start_time = effect.start_time,
			repeat_interval_start_time = effect.repeat_interval_start_time,
			playername = effect.playername,
			metadata = effect.metadata
		}
		if(inactive_effects[effect.playername] == nil) then
			inactive_effects[effect.playername] = {}
		end
		table.insert(inactive_effects[effect.playername], new_effect)
	end

	savetable.inactive_effects = inactive_effects
	savetable.last_effect_id = playereffects.last_effect_id

	local savestring = minetest.serialize(savetable)

	local filepath = minetest.get_worldpath().."/playereffects.mt"
	local file = io.open(filepath, "w")
	if file then
		file:write(savestring)
		io.close(file)
		minetest.log("info", "[playereffects] Wrote playereffects data into "..filepath..".")
	else
		minetest.log("error", "[playereffects] Failed to write playereffects data into "..filepath..".")
	end
end

--[=[ Callbacks ]=]
--[[ Cancel all effects on player death ]]
minetest.register_on_dieplayer(function(player)
	local effects = playereffects.get_player_effects(player:get_player_name())
	for e=1,#effects do
		if(playereffects.effect_types[effects[e].effect_type_id].cancel_on_death == true) then
			playereffects.cancel_effect(effects[e].effect_id)
		end
	end
end)

minetest.register_on_leaveplayer(function(player)
	local leave_time = os.time()
	local playername = player:get_player_name()
	local effects = playereffects.get_player_effects(playername)

	playereffects.hud_clear(player)

	if(playereffects.inactive_effects[playername] == nil) then
		playereffects.inactive_effects[playername] = {}
	end
	for e=1,#effects do
		local new_duration = effects[e].time_left - os.difftime(leave_time, effects[e].start_time)
		local new_effect = effects[e]
		new_effect.time_left = new_duration
		table.insert(playereffects.inactive_effects[playername], new_effect)
		playereffects.cancel_effect(effects[e].effect_id)
	end
end)

minetest.register_on_shutdown(function()
	minetest.log("info", "[playereffects] Server shuts down. Rescuing data into playereffects.mt")
	playereffects.save_to_file()
end)

minetest.register_on_joinplayer(function(player)
	local playername = player:get_player_name()

	-- load all the effects again (if any)
	if(playereffects.inactive_effects[playername] ~= nil) then
		for i=1,#playereffects.inactive_effects[playername] do
			local effect = playereffects.inactive_effects[playername][i]
			playereffects.apply_effect_type(effect.effect_type_id, effect.time_left, player, effect.repeat_interval_time_left)
		end
		playereffects.inactive_effects[playername] = nil
	end
end)

playereffects.globalstep_timer = 0
playereffects.autosave_timer = 0
minetest.register_globalstep(function(dtime)
	playereffects.globalstep_timer = playereffects.globalstep_timer + dtime
	playereffects.autosave_timer = playereffects.autosave_timer + dtime
	-- Update HUDs of all players
	if(playereffects.globalstep_timer >= 1) then
		playereffects.globalstep_timer = 0

		local players = minetest.get_connected_players()
		for p=1,#players do
			playereffects.hud_update(players[p])
		end
	end
	-- Autosave into file
	if(playereffects.use_autosave == true and playereffects.autosave_timer >= playereffects.autosave_time) then
		playereffects.autosave_timer = 0
		minetest.log("info", "[playereffects] Autosaving mod data to playereffects.mt ...")
		playereffects.save_to_file()
	end
end)

--[=[ HUD ]=]
function playereffects.hud_update(player)
	if(playereffects.use_hud == true) then
		local now = os.time()
		local playername = player:get_player_name()
		local hudinfos = playereffects.hudinfos[playername]
		if(hudinfos ~= nil) then
			for effect_id, hudinfo in pairs(hudinfos) do
				local effect = playereffects.effects[effect_id]
				if(effect ~= nil and hudinfo.text_id ~= nil) then
					local description = playereffects.effect_types[effect.effect_type_id].description
					local repeat_interval = playereffects.effect_types[effect.effect_type_id].repeat_interval
					if(repeat_interval ~= nil) then
						local repeat_interval_time_left = os.difftime(effect.repeat_interval_start_time + effect.repeat_interval_time_left, now)
						player:hud_change(hudinfo.text_id, "text", description .. " ("..tostring(effect.time_left).."/"..tostring(repeat_interval_time_left) .. "s )")
					else
						local time_left = os.difftime(effect.start_time + effect.time_left, now)
						player:hud_change(hudinfo.text_id, "text", description .. " ("..tostring(time_left).." s)")
					end
				end
			end
		end
	end
end

function playereffects.hud_clear(player)
	if(playereffects.use_hud == true) then
		local playername = player:get_player_name()
		local hudinfos = playereffects.hudinfos[playername]
		if(hudinfos ~= nil) then
			for effect_id, hudinfo in pairs(hudinfos) do
				local effect = playereffects.effects[effect_id]
				if(hudinfo.text_id ~= nil) then
					player:hud_remove(hudinfo.text_id)
				end
				if(hudinfo.icon_id ~= nil) then
					player:hud_remove(hudinfo.icon_id)
				end
				playereffects.hudinfos[playername][effect_id] = nil
			end
		end
	end
end

function playereffects.hud_effect(effect_type_id, player, pos, time_left, repeat_interval_time_left)
	local text_id, icon_id
	local effect_type = playereffects.effect_types[effect_type_id]
	if(playereffects.use_hud == true and effect_type.hidden == false) then
		local color
		if(playereffects.effect_types[effect_type_id].cancel_on_death == true) then
			color = 0xFFFFFF
		else
			color = 0xF0BAFF
		end
		local description = playereffects.effect_types[effect_type_id].description
		local text
		if(repeat_interval_time_left ~= nil) then
			text =  description .. " ("..tostring(time_left).."/"..tostring(repeat_interval_time_left) .. "s )"
		else
			text = description .. " ("..tostring(time_left).." s)"
		end
		text_id = player:hud_add({
			hud_elem_type = "text",
			position = { x = 1, y = 0.3 },
			name = "effect_"..effect_type_id,
			text = text,
			scale = { x = 170, y = 20},
			alignment = { x = -1, y = 0 },
			direction = 1,
			number = color,
			offset = { x = -5, y = pos*20 }
		})
		if(playereffects.effect_types[effect_type_id].icon ~= nil) then
			icon_id = player:hud_add({
				hud_elem_type = "image",
				scale = { x = 1, y = 1 },
				position = { x = 1, y = 0.3 },
				name = "effect_icon_"..effect_type_id,
				text = playereffects.effect_types[effect_type_id].icon,
				alignment = { x = -1, y=0 },
				direction = 0,
				offset = { x = -186, y = pos*20 },
			})
		end
	else
		text_id = nil
		icon_id = nil
	end
	return text_id, icon_id
end

-- LOAD EXAMPLES
if(playereffects.use_examples == true) then
	dofile(minetest.get_modpath(minetest.get_current_modname()).."/examples.lua")
end


code:
name = playereffects
description = Framework for temporary effects for players.
depends =
optional_depends =
author = wuzzy
title = Player Effects


code:
--[[
	Settings for Player Effects
]]

-- Wheather to use the HUD to expose the active effects to players (true or false)
playereffects.use_hud = true

-- Wheather to use autosave (true or false)
playereffects.use_autosave = true

-- The time interval between autosaves, in seconds (only used when use_autosave is true)
playereffects.autosave_time = 10

-- If true, this loads some examples from example.lua.
playereffects.use_examples = false


code:
# playerfactions

A simple mod which allows player created factions. Not very useful on its own, it becomes very powerful when combined with other mods.

## Usage
We can choose a mode : single or multi factions.
By default players can only be in one faction at a time. If you want to allow players to join multiple factions at once, add `player_factions.mode_unique_faction = false` to minetest.conf

Parameters marked with square brackets ([]) are optional; most of these are only used if mode_unique_faction is false.

There is an admin privs to enable every functions for every faction : playerfactions_admin

These commands can be used by anyone:

- `/factions create <faction> <password>`: Create a new faction
- `/factions list`: List available factions
- `/factions info <faction>`: See information on a faction. For faction with lot of member we can choose the length of the member's list that's going to be shown by adding `player_factions.max_members_list = int` to minetest.conf, default is 50.
- `/factions player_info <player>` See information on a player : joined, owned and administered factions.
- `/factions join <faction> <password>`: Join an existing faction
- `/factions leave [faction]`: Leave your faction

These commands can only be used by faction owners and players with the playerfactions_admin privilege:

- `/factions kick <player> [faction]`: Kick someone from your faction
- `/factions disband <password> [faction]`: Disband your faction
- `/factions passwd <password> [faction]`: Change your faction's password
- `/factions chown <player> <password> [faction]`: Transfer ownership of your faction

This command can only be used by players with the playerfactions_admin privilege:
- `/factions invite <player> <faction>`: Add player to a faction

## Translations

If you know English and another language, please submit a translation! It would be greatly appreciated, and your name will be added to the acknowledgements at the bottom of this page. Thanks!

## Mod integration

The following mods have optional support for `playerfactions`:

- `areas`: Protect faction territory using areas. [link](https://github.com/minetest-mods/areas)
- `protector`: Allow faction to be added as a member to protection blocks. [link](https://notabug.org/TenPlus1/protector)

### Using `playerfactions` in your own mod

I strongly recommend reading through the `init.lua` file; the functions at the top give you a pretty good idea of how to use it, but just in case you're short on time I'll list the most important functions below.

- `factions.version` is a variable made to check the version of the playerfactions mod to assert compatibility:
* factions.version == nil for firsts version of playerfactions mod
* factions.version == 2 is the first time this variable is added, with adding multi-faction mode
- `player_is_in_faction(fname, player_name)`: `true` if the player is in the faction, `nil` in other cases (faction or player doesn't exists or player is not a member of the faction)
- `get_facts()`: Get a copied table with all data. The structure is :
```
{["name_of_faction1"]={
      ["owner"]=name_of_the_owner,
      ["members"]={["name_of_a_member1"]=true, ["name_of_a_member2"]=true}
  }}
```
- `get_player_faction(player)`: Get a string with the faction a player belongs to, `nil` if they haven't joined a faction. In multi-faction mode, it will return the oldest created faction which player is into. (it's not necessarily the one they joined first. It checks the facts variable from the top)
- `get_player_factions(player)`: Get a table with the faction(s) a player belongs to, `nil` if they haven't joined a faction. The structure is: {name_of_faction1, name_of_faction2}
- `get_owned_factions(player)`: Get a table with the faction(s) owned by a player, `nil` if they have the ownership of no faction. The structure is: {name_of_faction1, name_of_faction2}
- `get_owner(faction)`: Get the owner of a faction
- `chown(fname, owner)`: Change the owner of a faction
- `register_faction(faction, player, password)`: Create a new faction
- `disband_faction(faction)`: Disband a faction
- `get_password(faction)`: Gets a faction's password
- `set_password(faction, password)`: Sets a faction's password
- `join_faction(faction, player)`: Sets the given player as belonging to this faction
- `leave_faction(faction, player)`: Remove the given player from the specified faction

Note that all of these functions have sanity checks : if faction or player does not exists, it return false. If operation succeed, it return true or the needed value.

## Acknowledgements

Multi-faction mode and various other improvements added by [Kalio_42](https://git.leagueh.xyz/Kalio_42).

This mod is loosely based off of the (unmaintained) [factions mod made by Jonjeg](https://github.com/Jonjeg/factions).


code:
-- Translation support
local S = minetest.get_translator("playerfactions")

minetest.register_privilege("playerfactions_admin", {description = S("Allow the use of all playerfactions commands"), give_to_singleplayer = false})

-- Data
factions = {}
-- This variable "version" can be used by other mods to check the compatibility of this mod
factions.version = 2

local facts = {}
local storage = minetest.get_mod_storage()
if storage:get_string("facts") ~= "" then
	facts = minetest.deserialize(storage:get_string("facts"))
end
-- Fix factions
for fname, fact in pairs(facts) do
	if fact.members == nil then
		fact.members = {}
	end
end

factions.mode_unique_faction = minetest.settings:get_bool("player_factions.mode_unique_faction", true)
factions.max_members_list = tonumber(minetest.settings:get("player_factions.max_members_list")) or 50

local function save_factions()
	storage:set_string("facts", minetest.serialize(facts))
end

local function table_copy(data)
	local copy = {}
	if type(data) == "table" then
		for k,v in pairs(data) do
			copy[k]=table_copy(v)
		end
		return copy
	else
		return data
	end
end

-- Data manipulation
function factions.get_facts()
	return table_copy(facts)
end

function factions.player_is_in_faction(fname, player_name)
	if not minetest.player_exists(player_name) or facts[fname] == nil then
		return false
	end
	return facts[fname].members[player_name]
end

function factions.get_player_faction(name)
	if not minetest.player_exists(name) then
		return false
	end
	minetest.log("warning", "Function factions.get_player_faction() is deprecated in favor of factions.get_player_factions(). Please check updates of mods depending on playerfactions.")
	for fname, fact in pairs(facts) do
		if fact.members[name] then
			return fname
		end
	end
	return nil
end

function factions.get_player_factions(name)
	if not minetest.player_exists(name) then
		return false
	end
	local player_factions = nil
	for fname, fact in pairs(facts) do
		if fact.members[name] then
			if not player_factions then
				player_factions = {}
			end
			table.insert(player_factions, fname)
		end
	end
	return player_factions
end

function factions.get_owned_factions(name)
	local own_factions = nil
	for fname, fact in pairs(facts) do
		if fact.owner == name then
			if not own_factions then
				own_factions = {}
			end
			table.insert(own_factions, fname)
		end
	end
	return own_factions
end

function factions.get_administered_factions(name)
	local adm_factions = nil
	for fname, fact in pairs(facts) do
		if minetest.get_player_privs(name).playerfactions_admin or fact.owner == name then
			if not adm_factions then
				adm_factions = {}
			end
			table.insert(adm_factions, fname)
		end
	end
	return adm_factions
end

function factions.get_owner(fname)
	if facts[fname] == nil then
		return false
	end
	return facts[fname].owner
end

function factions.chown(fname, owner)
	if facts[fname] == nil then
		return false
	end
	facts[fname].owner = owner
	save_factions()
	return true
end

function factions.register_faction(fname, founder, pw)
	if facts[fname] ~= nil then
		return false
	end
	facts[fname] = {
		name = fname,
		owner = founder,
		password = pw,
		members = {[founder] = true}
	}
	save_factions()
	return true
end

function factions.disband_faction(fname)
	if facts[fname] == nil then
		return false
	end
	facts[fname] = nil
	save_factions()
	return true
end

function factions.get_password(fname)
	if facts[fname] == nil then
		return false
	end
	return facts[fname].password
end

function factions.set_password(fname, password)
	if facts[fname] == nil then
		return false
	end
	facts[fname].password = password
	save_factions()
	return true
end

function factions.join_faction(fname, player)
	if facts[fname] == nil or not minetest.player_exists(player) then
		return false
	end
	facts[fname].members[player] = true
	save_factions()
	return true
end

function factions.leave_faction(fname, player_name)
	if facts[fname] == nil or not minetest.player_exists(player_name) then
		return false
	end
	facts[fname].members[player_name] = nil
	save_factions()
	return true
end

-- Chat commands
local function handle_command(name, param)
	local params = {}
	for p in string.gmatch(param, "[^%s]+") do
		table.insert(params, p)
	end
	if params == nil then
		minetest.chat_send_player(name, S("Unknown subcommand. Run '/help factions' for help."))
	end
	local action = params[1]
	if action == "create" then
		local faction_name = params[2]
		local password = params[3]
		if factions.mode_unique_faction and factions.get_player_faction(name) ~= nil then
			minetest.chat_send_player(name, S("You are already in a faction."))
		elseif faction_name == nil then
			minetest.chat_send_player(name, S("Missing faction name."))
		elseif password == nil then
			minetest.chat_send_player(name, S("Missing password."))
		elseif facts[faction_name] ~= nil then
			minetest.chat_send_player(name, S("That faction already exists."))
		else
			factions.register_faction(faction_name, name, password)
			minetest.chat_send_player(name, S("Registered @1.", faction_name))
			return true
		end
	elseif action == "disband" then
		local password = nil
		local faction_name = nil
		local own_factions = factions.get_administered_factions(name)
		local number_factions = #own_factions
		if number_factions == 0 then
			minetest.chat_send_player(name, S("You are the owner of no faction."))
			return false
		elseif #params == 1 then
			minetest.chat_send_player(name, S("Missing password."))
			return false
		elseif #params == 2 and number_factions == 1 then
			password = params[2]
			faction_name = own_factions[1]
		elseif #params >= 3 then
			faction_name = params[3]
			password = params[2]
		end
		if password == nil then
			minetest.chat_send_player(name, S("Missing password."))
		elseif faction_name == nil then
			minetest.chat_send_player(name, S("You are the owner of many factions, you have to choose one of them: @1.", table.concat(own_factions, ", ")))
		elseif not facts[faction_name] then
			minetest.chat_send_player(name, S("This faction doesn't exists."))
		elseif name ~= factions.get_owner(faction_name) and not minetest.get_player_privs(name).playerfactions_admin then
			minetest.chat_send_player(name, S("Permission denied: You are not the owner of this faction, and don't have the playerfactions_admin privilege."))
		elseif password ~= factions.get_password(faction_name) then
			minetest.chat_send_player(name, S("Permission denied: Wrong password."))
		else
			factions.disband_faction(faction_name)
			minetest.chat_send_player(name, S("Disbanded @1.", faction_name))
			return true
		end
	elseif action == "list" then
		local faction_list = {}
		for k, f in pairs(facts) do
			table.insert(faction_list, k)
		end
		if #faction_list ~= 0 then
			minetest.chat_send_player(name, S("Factions (@1): @2.", #faction_list, table.concat(faction_list, ", ")))
		else
			minetest.chat_send_player(name, S("There are no factions yet."))
		end
		return true
	elseif action == "info" then
		local faction_name = params[2]
		if faction_name == nil then
			local player_factions = factions.get_player_factions(name) or {}
			if #player_factions == 1 then
				faction_name = player_factions[1]
			elseif #player_factions == 0 then
				minetest.chat_send_player(name, S("You are not a member of a faction."))
			else
				minetest.chat_send_player(name, S("You are in many factions, you have to choose one of them: @1.", table.concat(player_factions, ", ")))
				return false
			end
		elseif facts[faction_name] == nil then
			minetest.chat_send_player(name, S("This faction doesn't exists."))
		else
			local fmembers = ""
			if table.getn(facts[faction_name].members) > factions.max_members_list then
				fmembers = S("The faction has more than @1 members, the members list can't be shown.", factions.max_members_list)
			else
				for play,_ in pairs(facts[faction_name].members) do
					if fmembers == "" then
						fmembers = play
					else
						fmembers = fmembers..", "..play
					end
				end
			end
			minetest.chat_send_player(name, S("Name: @1\nOwner: @2\nMembers: @3", faction_name, factions.get_owner(faction_name), fmembers))
			if factions.get_owner(faction_name) == name or minetest.get_player_privs(name).playerfactions_admin then
				minetest.chat_send_player(name, S("Password: @1", factions.get_password(faction_name)))
			end
		end
	elseif action == "player_info" then
		local player_name = params[2]
		local player_factions = factions.get_player_factions(player_name)
		if not player_factions then
			minetest.chat_send_player(name, S("This player doesn't exists or is in no faction"))
			return false
		else
			local str_owner = ""
			local str_member = ""
			for _,v in ipairs(player_factions) do
				if str_member == "" then
					str_member = str_member..v
				else
					str_member = str_member..", "..v
				end
			end
			minetest.chat_send_player(name, "@1 is in the following factions: @2.", player_name, str_member, str_owner)
			local owned_factions = factions.get_owned_factions(player_name)
			if not owned_factions then
				minetest.chat_send_player(name, S("This player is the owner of no faction."))
			else
				for _,v in ipairs(owned_factions) do
					if str_owner == "" then
						str_owner = str_owner..v
					else
						str_owner = str_owner..", "..v
					end
				end
				minetest.chat_send_player(name, S("This player is the owner of the following factions: @1.", str_owner))
			end
			if minetest.get_player_privs(player_name).playerfactions_admin then
				minetest.chat_send_player(name, S("@1 has the playerfactions_admin privilege so they can admin every faction.", player_name))
			end
		end
	elseif action == "join" then
		local faction_name = params[2]
		local password = params[3]
		if factions.get_player_faction(name) ~= nil and factions.mode_unique_faction then
			minetest.chat_send_player(name, S("You are already in a faction."))
		elseif facts[faction_name] == nil then
			minetest.chat_send_player(name, S("The faction @1 doesn't exist.", faction_name))
		elseif factions.get_password(faction_name) ~= password then
			minetest.chat_send_player(name, S("Permission denied: Wrong password."))
		else
			if factions.join_faction(faction_name, name) then
				minetest.chat_send_player(name, S("Joined @1.", faction_name))
				return true
			else
				minetest.chat_send_player(name, S("Error on joining."))
				return false
			end
		end
	elseif action == "leave" then
		local player_factions = factions.get_player_factions(name)
		local number_factions = table.getn(player_factions)
		local faction_name = nil
		if number_factions == 0 then
			minetest.chat_send_player(name, S("You are not in a faction."))
			return false
		elseif #params == 1 then
		 	if number_factions == 1 then
				faction_name = player_factions[1]
			else
				minetest.chat_send_player(name, S("You are in many factions, you have to choose one of them: @1.", table.concat(player_factions, ", ")))
				return false
			end
		elseif #params >= 1 and facts[params[2]] ~= nil then
			faction_name = params[2]
		end
		if faction_name == nil then
			minetest.chat_send_player(name, S("The given faction doesn't exists."))
		elseif factions.get_owner(faction_name) == name then
			minetest.chat_send_player(name, S("You cannot leave your own faction, change owner or disband it."))
		else
			if factions.leave_faction(faction_name, name) then
				minetest.chat_send_player(name, S("Left @1.", faction_name))
				return true
			else
				minetest.chat_send_player(name, S("Error on leaving faction."))
				return false
			end
		end
	elseif action == "kick" then
		local target = nil
		local faction_name = nil
		local own_factions = factions.get_administered_factions(name)
		local number_factions = table.getn(own_factions)
		if number_factions == 0 then
			minetest.chat_send_player(name, S("You are the owner of no faction, you can't use this command."))
			return false
		elseif #params == 2 and number_factions == 1 then
			target = params[2]
			faction_name = own_factions[1]
		elseif #params >= 3 then
			faction_name = params[3]
			target = params[2]
		end
		if faction_name == nil then
			minetest.chat_send_player(name, S("You are the owner of many factions, you have to choose one of them: @1.", table.concat(own_factions, ", ")))
		elseif target == nil then
			minetest.chat_send_player(name, S("Missing player name."))
		elseif factions.get_owner(faction_name) ~= name and not minetest.get_player_privs(name).playerfactions_admin then
			minetest.chat_send_player(name, S("Permission denied: You are not the owner of this faction, and don't have the playerfactions_admin privilege."))
		elseif not facts[faction_name].members[target] then
			minetest.chat_send_player(name, S("This player is not in the specified faction."))
		elseif target == factions.get_owner(faction_name) then
			minetest.chat_send_player(name, S("You cannot kick the owner of a faction, use '/factions chown <player> [faction]' to change the ownership."))
		else
			if factions.leave_faction(faction_name, target) then
				minetest.chat_send_player(name, S("Kicked @1 from faction.", target))
				return true
			else
				minetest.chat_send_player(name, S("Error kicking @1 from faction.", target))
				return false
			end
		end
	elseif action == "passwd" then
		local password = nil
		local faction_name = nil
		local own_factions = factions.get_administered_factions(name)
		local number_factions = table.getn(own_factions)
		if #params == 1 then
			minetest.chat_send_player(name, S("Missing password."))
			return false
		elseif number_factions == 0 then
			minetest.chat_send_player(name, S("You are the owner of no faction, you can't use this command."))
			return false
		elseif #params == 2 and number_factions == 1 then
			password = params[2]
			faction_name = own_factions[1]
		elseif #params >= 3 then
			faction_name = params[3]
			password = params[2]
		end
		if faction_name == nil then
			minetest.chat_send_player(name, S("You are the owner of many factions, you have to choose one of them: @1.", table.concat(own_factions, ", ")))
		elseif password == nil then
			minetest.chat_send_player(name, S("Missing password."))
		elseif factions.get_owner(faction_name) ~= name and not minetest.get_player_privs(name).playerfactions_admin then
			minetest.chat_send_player(name, S("Permission denied: You are not the owner of this faction, and don't have the playerfactions_admin privilege."))
		else
			if factions.set_password(faction_name, password) then
				minetest.chat_send_player(name, S("Password has been updated."))
				return true
			else
				minetest.chat_send_player(name, S("Failed to change password."))
				return false
			end
		end
	elseif action == "chown" then
		local own_factions = factions.get_administered_factions(name)
		local number_factions = table.getn(own_factions)
		local faction_name = nil
		local target = nil
		local password = nil
		if #params < 3 then
			if params[2] ~= nil and minetest.player_exists(params[2]) then
				minetest.chat_send_player(name, S("Missing password."))
				return false
			else
				minetest.chat_send_player(name, S("Missing player name."))
				return false
			end
		elseif number_factions == 0 then
			minetest.chat_send_player(name, S("You are the owner of no faction, you can't use this command."))
			return false
		elseif number_factions == 1 and #params == 3 then
			faction_name = own_factions[1]
			target = params[2]
			password = params[3]
		elseif #params >= 4 then
			faction_name = params[4]
			target = params[2]
			password = params[3]
		end
		if faction_name == nil then
			minetest.chat_send_player(name, S("You are the owner of many factions, you have to choose one of them: @1.", table.concat(own_factions, ", ")))
		elseif target == nil then
			minetest.chat_send_player(name, S("Missing player name."))
		elseif password == nil then
			minetest.chat_send_player(name, S("Missing password."))
		elseif name ~= factions.get_owner(faction_name) and not minetest.get_player_privs(name).playerfactions_admin then
			minetest.chat_send_player(name, S("Permission denied: You are not the owner of this faction, and don't have the playerfactions_admin privilege."))
		elseif not facts[faction_name].members[target] then
			minetest.chat_send_player(name, S("@1 isn't in your faction.", target))
		elseif password ~= factions.get_password(faction_name) then
			minetest.chat_send_player(name, S("Permission denied: Wrong password."))
		else
			if factions.chown(faction_name, target) then
				minetest.chat_send_player(name, S("Ownership has been transferred to @1.", target))
				return true
			else
				minetest.chat_send_player(name, S("Failed to transfer ownership."))
				return false
			end
		end
	elseif action == "invite" then
		if not minetest.get_player_privs(name).playerfactions_admin then
			minetest.chat_send_player(name, S("Permission denied: You can't use this command, playerfactions_admin priv is needed."))
		else
			local target = params[2]
			local faction_name = params[3]
			if facts[faction_name] == nil then
				minetest.chat_send_player(name, S("The faction @1 doesn't exist.", faction_name))
			elseif not minetest.player_exists(target) then
				minetest.chat_send_player(name, S("The player doesn't exist."))
			elseif factions.mode_unique_faction and factions.get_player_faction(target) ~= nil then
				minetest.chat_send_player(name, S("The player is already in the faction \"@1\".",factions.get_player_faction(target)))
			else
				if factions.join_faction(faction_name, target) then
					minetest.chat_send_player(name, S("@1 is now a member of the faction @2.", target, faction_name))
					return true
				else
					minetest.chat_send_player(name, S("Error on adding @1 into @2.", target, faction_name))
					return true
				end
			end
		end
	else
		minetest.chat_send_player(name, S("Unknown subcommand. Run '/help factions' for help."))
	end
	return false
end

minetest.register_chatcommand("factions", {
	params = "create <faction> <password>: "..S("Create a new faction").."\n"
	.."list: "..S("List available factions").."\n"
	.."info <faction>: "..S("See information on a faction").."\n"
	.."player_info <player>: "..S("See information on a player").."\n"
	.."join <faction> <password>: "..S("Join an existing faction").."\n"
	.."leave [faction]: "..S("Leave your faction").."\n"
	.."kick <player> [faction]: "..S("Kick someone from your faction or from the given faction").."\n"
	.."disband <password> [faction]: "..S("Disband your faction or the given faction").."\n"
	.."passwd <password> [faction]: "..S("Change your faction's password or the password of the given faction").."\n"
	.."chown <player> [faction]: "..S("Transfer ownership of your faction").."\n"
	.."invite <player> <faction>: "..S("Add player to a faction, you need playerfactions_admin privs").."\n",

	description = "",
	privs = {},
	func = handle_command
})


code:
# textdomain: playerfactions

### init.lua ###

@1 has the playerfactions_admin privilege so they can admin every faction.=@1 a le privilège playerfactions_admin et peut donc administrer toutes les factions.

@1 is in the following factions: @2.=@1 est dans les factions suivantes : @2.
@1 is now a member of the faction @2.=@1 est maintenant membre de la faction @2.
@1 isn't in your faction.=@1 n’est pas dans votre faction.
Add player to a faction, you need playerfactions_admin privs=Ajoute un joueur à une faction, nécessite le privilège playerfactions_admin
Allow the use of all playerfactions commands=Autorise l’utilisation de toutes les commandes de playerfactions

Change your faction's password or the password of the given faction=Change le mot de passe de votre faction ou la faction donnée

Create a new faction=Crée une nouvelle faction
Disband your faction or the given faction=Dissoudre votre faction ou la faction donnée
Disbanded @1.=@1 est dissoute.
Error kicking @1 from faction.=Erreur lors de la tentative de virer @1 de la faction.
Error on adding @1 into @2.=Erreur lors de l’ajout de @1 dans @2.
Error on joining.=Erreur lors de la tentative de rejoindre la faction.
Error on leaving faction.=Erreur lors de la tentative de quitter la faction.
Factions (@1): @2.=Factions (@1) : @2.
Failed to change password.=Echec du changement de mot de passe.
Failed to transfer ownership.=Echec du transfert de propriété.
Join an existing faction=Rejoindre une faction existante.
Joined @1.=Vous avez rejoint @1.
Kick someone from your faction or from the given faction=Virer quelqu’un de votre faction ou de la faction donnée.
Kicked @1 from faction.=@1 a été viré de la faction.
Leave your faction=Quitter votre faction
Left @1.=Vous avez quitté @1.
List available factions=Liste les factions disponibles
Missing faction name.=Nom de faction manquant.
Missing password.=Mot de passe manquant.
Missing player name.=Nom de joueur manquant.
Name: @1@nOwner: @2@nMembers: @3=Nom : @1@nPropriétaire : @2@nMembres : @3
Ownership has been transferred to @1.=La propriété a été transférée à @2.
Password has been updated.=Le mot de passe a été mis à jour.
Password: @1=Mot de passe : @1
Permission denied: Wrong password.=Permission refusée : mauvais mot de passe.

Permission denied: You are not the owner of this faction, and don't have the playerfactions_admin privilege.=Permission refusée : vous n’êtes pas le propriétaire de cette faction, et n’avez pas le privilège playerfactions_admin.

Permission denied: You can't use this command, playerfactions_admin priv is needed.=Permission refusée : vous ne pouvez pas utiliser cette commande, le privilège playerfactions_admin est nécessaire.

Registered @1.=@1 enregistrée.
See information on a faction=Voir les informations d’une faction
See information on a player=Voir les informations d’un joueur
That faction already exists.=Cette faction existe déjà.
The faction @1 doesn't exist.=La faction @1 n’existe pas.

The faction has more than @1 members, the members list can't be shown.=Cette faction a plus que @1 membres, la liste des membres ne peut pas être affichée.

The given faction doesn't exists.=La faction en question n’existe pas.
The player doesn't exist.=Le joueur n’existe pas.
The player is already in the faction "@1".=Le joueur est déjà dans la faction "@1".
There are no factions yet.=Il n’y a pas encore de factions.
This faction doesn't exists.=Cette faction n’existe pas.
This player doesn't exists or is in no faction=Ce joueur n’existe pas ou n’est dans aucune faction.
This player is not in the specified faction.=Le joueur n’est pas dans la faction spécifiée.
This player is the owner of no faction.=Ce joueur n’est propriétaire d’aucune faction.
This player is the owner of the following factions: @1.=Ce joueur n’est le propriétaire d’aucune des factions suivantes : @1.
Transfer ownership of your faction=Transfert la propriété de votre faction
Unknown subcommand. Run '/help factions' for help.=Sous-commande inconnue. Faites '/help factions' pour l’aide.
You are already in a faction.=Vous êtes déjà dans une faction.

You are in many factions, you have to choose one of them: @1.=Vous êtes dans plusieurs factions, vous devez choisir l’une d’entre elles : @1.

You are not in a faction.=Vous n’êtes dans aucune faction.

You are the owner of many factions, you have to choose one of them: @1.=Vous êtes propriétaire de plusieurs factions, vous devez choisir l’une d’entre elles : @1.

You are the owner of no faction, you can't use this command.=Vous n’êtes propriétaire d’aucune faction, vous ne pouvez pas utiliser cette commande.
You are the owner of no faction.=Vous n’êtes propriétaire d’aucune faction.

You cannot kick the owner of a faction, use '/factions chown <player> [faction]' to change the ownership.=Vous ne pouvez pas virer le propriétaire de sa faction, utilisez '/factions chown <player> [faction]' pour changer le propriétaire.

You cannot leave your own faction, change owner or disband it.=Vous ne pouvez pas quitter votre propre faction, changez le propriétaire ou dissolvez la.


code:
# textdomain: playerfactions

### init.lua ###

@1 has the playerfactions_admin privilege so they can admin every faction.=

@1 is in the following factions: @2.=
@1 is now a member of the faction @2.=
@1 isn't in your faction.=
Add player to a faction, you need playerfactions_admin privs=
Allow the use of all playerfactions commands=

Change your faction's password or the password of the given faction=

Create a new faction=
Disband your faction or the given faction=
Disbanded @1.=
Error kicking @1 from faction.=
Error on adding @1 into @2.=
Error on joining.=
Error on leaving faction.=
Factions (@1): @2.=
Failed to change password.=
Failed to transfer ownership.=
Join an existing faction=
Joined @1.=
Kick someone from your faction or from the given faction=
Kicked @1 from faction.=
Leave your faction=
Left @1.=
List available factions=
Missing faction name.=
Missing password.=
Missing player name.=
Name: @1@nOwner: @2@nMembers: @3=
Ownership has been transferred to @1.=
Password has been updated.=
Password: @1=
Permission denied: Wrong password.=

Permission denied: You are not the owner of this faction, and don't have the playerfactions_admin privilege.=

Permission denied: You can't use this command, playerfactions_admin priv is needed.=

Registered @1.=
See information on a faction=
See information on a player=
That faction already exists.=
The faction @1 doesn't exist.=

The faction has more than @1 members, the members list can't be shown.=

The given faction doesn't exists.=
The player doesn't exist.=
The player is already in the faction "@1".=
There are no factions yet.=
This faction doesn't exists.=
This player doesn't exists or is in no faction=
This player is not in the specified faction.=
This player is the owner of no faction.=
This player is the owner of the following factions: @1.=
Transfer ownership of your faction=
Unknown subcommand. Run '/help factions' for help.=
You are already in a faction.=

You are in many factions, you have to choose one of them: @1.=

You are not in a faction.=

You are the owner of many factions, you have to choose one of them: @1.=

You are the owner of no faction, you can't use this command.=
You are the owner of no faction.=

You cannot kick the owner of a faction, use '/factions chown <player> [faction]' to change the ownership.=

You cannot leave your own faction, change owner or disband it.=


code:
name = playerfactions
min_minetest_version = 5.0.0


code:
player_factions.mode_unique_faction (Enable faction mode) bool true
player_factions.max_members_list (The max number of members shown with info command) int 50


code:
PlayerPlus mod for minetest

This mod lets the player move faster when walking on ice, slows down the player
when walking on snow, makes touching a cactus hurt and suffocates player when
their head is inside a solid node... enjoy!

https://forum.minetest.net/viewtopic.php?t=10090&p=153667

- 0.1 - Initial release
- 0.2 - 3d_armor mod compatibility
- 0.3 - Optimized code
- 0.4 - Added suffocation when inside nodes
- 0.5 - Slow down when walking in water
- 0.6 - Code tidy and tweak, increased damage by cactus and suffocation
- 0.7 - Added global 'playerplus' table to hold node names gathered around player
- 0.8 - Player knock-back added
- 0.9 - 'on_walk_over' function support added for nodes
- 1.0 - Update to newer functions, requires Minetest 0.4.16 and above to run
- 1.1 - Added support for player_monoids mod (thanks tacotexmex)
- 1.2 - Added POVA support, tweaked code slightly
- 1.3 - Add setting under Advanced to enable older sneak glitch movement
- 1.4 - Add minetest 5.0 check for knockback y_offset

API:

Every second the mod checks which node the player is standing on, which node is
at foot and head level and stores inside a global table to be used by mods:

- playerplus[name].nod_stand
- playerplus[name].nod_foot
- playerplus[name].nod_head

Older Sneak-glitch:

When enabled this allows players to use the old sneak glitch movement that lets
you climb sneak ladders and jump double blocks when using sneak+jump,

old_sneak = false

Player Knock-back:

This feature is enabled by default and can be turned off by adding this line to
your minetest.conf file:

player_knockback = false

Knock-back is determined by tool strength or a specific 'knockback' value which
can be added to the tool damage_groups itself.  A player with the privelage
'no_knockback' will not be affected when it's enabled.

Node 'on_walk_over' function:

When added to a node definition the function will be called whenever a player
walks on top of the node e.g.

	on_walk_over = function(pos, node, player)
		print ("---", node.name, player:get_player_name() )
	end


code:
--[[
	walking on ice makes player walk faster,
	stepping through snow slows player down,
	touching a cactus hurts player,
	suffocation when head is inside solid node,
	player knock-back effects when punched.

	PlayerPlus by TenPlus1
]]

playerplus = {}

-- detect minetest 5.0
local mt50 = minetest.registered_nodes["default:permafrost"]

-- cache if player_monoids mod active?
local monoids = minetest.get_modpath("player_monoids")
local pova_mod = minetest.get_modpath("pova")

-- get node but use fallback for nil or unknown
local node_ok = function(pos, fallback)

	fallback = fallback or "air"

	local node = minetest.get_node_or_nil(pos)

	if node and minetest.registered_nodes[node.name] then
		return node.name
	end

	return fallback
end

local armor_mod = minetest.get_modpath("3d_armor")
local time = 0

minetest.register_globalstep(function(dtime)

	time = time + dtime

	-- every 1 second
	if time < 1 then
		return
	end

	-- reset time for next check
	time = 0

	-- define locals outside loop
	local name, pos, ndef, def, nslow, nfast

	-- loop through players
	for _,player in ipairs(minetest.get_connected_players()) do

		-- who am I?
		name = player:get_player_name()

		-- where am I?
		pos = player:get_pos()

		-- what is around me?
		pos.y = pos.y - 0.1 -- standing on
		playerplus[name].nod_stand = node_ok(pos)

		-- Does the node below me have an on_walk_over function set?
		ndef = minetest.registered_nodes[playerplus[name].nod_stand]
		if ndef and ndef.on_walk_over then
			ndef.on_walk_over(pos, ndef, player)
		end

		pos.y = pos.y + 1.5 -- head level
		playerplus[name].nod_head = node_ok(pos)

		pos.y = pos.y - 1.2 -- feet level
		playerplus[name].nod_feet = node_ok(pos)

		pos.y = pos.y - 0.2 -- reset pos

		-- get player physics
		def = player:get_physics_override()

		if armor_mod and armor and armor.def then
			-- get player physics from armor
			def.speed = armor.def[name].speed or def.speed
			def.jump = armor.def[name].jump or def.jump
			def.gravity = armor.def[name].gravity or def.gravity
		end

		-- apply speed changes
		if nfast and not playerplus[name].nfast then
			if monoids then
				playerplus[name].nfast = player_monoids.speed:add_change(
					player, def.speed + 0.4)
			elseif pova_mod then
				pova.add_override(name, "playerplus:nfast", {speed = 0.4})
				pova.do_override(player)
			else
				def.speed = def.speed + 0.4
			end

			playerplus[name].nfast = true

		elseif not nfast and playerplus[name].nfast then
			if monoids then
				player_monoids.speed:del_change(player, playerplus[name].nfast)
				playerplus[name].nfast = nil
			elseif pova_mod then
				pova.del_override(name, "playerplus:nfast")
				pova.do_override(player)
			else
				def.speed = def.speed - 0.4
			end

			playerplus[name].nfast = nil
		end

		-- apply slowdown changes
		if nslow and not playerplus[name].nslow then
			if monoids then
				playerplus[name].nslow = player_monoids.speed:add_change(
					player, def.speed - 0.3)
			elseif pova_mod then
				pova.add_override(name, "playerplus:nslow", {speed = -0.3})
				pova.do_override(player)
			else
				def.speed = def.speed - 0.3
			end

			playerplus[name].nslow = true

		elseif not nslow and playerplus[name].nslow then
			if monoids then
				player_monoids.speed:del_change(player, playerplus[name].nslow)
				playerplus[name].nslow = nil
			elseif pova_mod then
				pova.del_override(name, "playerplus:nslow")
				pova.do_override(player)
			else
				def.speed = def.speed + 0.3
			end

			playerplus[name].nslow = nil
		end

		-- set player physics
		if not monoids and not pova_mod then
			player:set_physics_override(def.speed, def.jump, def.gravity)
		end
--[[
		print ("Speed: " .. def.speed
			.. " / Jump: " .. def.jump
			.. " / Gravity: " .. def.gravity)
]]
		-- Is player suffocating inside a normal node without no_clip privs?
		local ndef = minetest.registered_nodes[playerplus[name].nod_head]

		if ndef.walkable == true
		and ndef.drowning == 0
		and ndef.damage_per_second <= 0
		and ndef.groups.disable_suffocation ~= 1
		and ndef.drawtype == "normal"
		and not minetest.check_player_privs(name, {noclip = true}) then

			if player:get_hp() > 0 then
				player:set_hp(player:get_hp() - 2)
			end
		end

		-- am I near a cactus?
		local near = minetest.find_node_near(pos, 1, "default:cactus")

		if near then

			-- am I touching the cactus? if so it hurts
			for _,object in pairs(minetest.get_objects_inside_radius(near, 1.1)) do

				if object:get_hp() > 0 then
					object:set_hp(object:get_hp() - 2)
				end
			end

		end

	end

end)

-- check for old sneak_glitch setting
local old_sneak = minetest.settings:get_bool("old_sneak")

-- set to blank on join (for 3rd party mods)
minetest.register_on_joinplayer(function(player)

	local name = player:get_player_name()

	playerplus[name] = {}
	playerplus[name].nod_head = ""
	playerplus[name].nod_feet = ""
	playerplus[name].nod_stand = ""

	-- apply old sneak glitch if enabled
	if old_sneak then
		player:set_physics_override({new_move = false, sneak_glitch = true})
	end
end)

-- clear when player leaves
minetest.register_on_leaveplayer(function(player)

	playerplus[ player:get_player_name() ] = nil
end)

-- add privelage to disable knock-back
minetest.register_privilege("no_knockback", {
		description = "Disables player knock-back effect",
		give_to_singleplayer = false})

-- is player knock-back effect enabled?
if minetest.settings:get_bool("player_knockback") ~= false then

minetest.register_entity("playerplus:temp", {
	physical = true,
	collisionbox = {-0.20, -1, -0.20, 0.20, 1, 0.20},
	visual_size = {x = 0, y = 0},
	visual = "sprite",
	textures = {"trans.png"},
	stepheight = 0.6,

	on_step = function(self, dtime)

		self.timer = (self.timer or 0) + dtime

		if self.timer > 1 then
			self.object:remove()
		end
	end
})

-- player knock-back function
local punchy = function(player, hitter, time_from_last_punch, tool_capabilities, dir, damage)

	local damage = 0

	-- get tool damage
	if tool_capabilities then

		local armor = player:get_armor_groups() or {}
		local tmp

		for group,_ in pairs( (tool_capabilities.damage_groups or {}) ) do

			tmp = time_from_last_punch / (tool_capabilities.full_punch_interval or 1.4)

			if tmp < 0 then
				tmp = 0.0
			elseif tmp > 1 then
				tmp = 1.0
			end

			damage = damage + (tool_capabilities.damage_groups[group] or 0) * tmp

		end

		-- check for knockback value
		if tool_capabilities.damage_groups["knockback"] then
			damage = tool_capabilities.damage_groups["knockback"]
		end

	end
	-- END tool damage

--	print ("---", player:get_player_name(), damage)

	if not dir then return end

	-- check if player has 'no_knockback' privelage
	local privs = minetest.get_player_privs(player:get_player_name())

	if privs["no_knockback"] then
		return
	end

	local y_offset = mt50 and -10 or 0
	local vel = damage * 2
	local pos = player:get_pos() ; pos.y = pos.y + 1.0
	local ent = minetest.add_entity(pos, "playerplus:temp")
	local obj = ent:get_luaentity()
	local yaw = player:get_look_horizontal() or 0 ; yaw = -yaw * (180 / 3.14) -- pi

	if obj and not player:get_attach() then

		player:set_attach(ent, "", {x = 0, y = y_offset, z = 0}, {x = 0, y = yaw, z = 0})

		ent:set_velocity({
			x = dir.x * vel,
			y = -1,
			z = dir.z * vel
		})

		ent:set_acceleration({
			x = dir.x * -1,
			y = 0,
			z = dir.z * -1
		})

		minetest.after(0.25, function()

			player:set_detach()

			ent:remove()
		end)

	else
		ent:remove()
	end
end

minetest.register_on_punchplayer(punchy)

end -- END if


code:
name = playerplus
author = TenPlus1
description = Add speed effects, suffocation and cactus damage to players.
depends =
optional_depends = default, 3d_armor, player_monoids, pova


code:
#    If enabled players are knocked back when hit by mobs or other players
player_knockback (Player knockback) bool true
#    If enabled the players use the old sneak glitch and movements
old_sneak (Use old sneak glitch) bool false


code:
# Auto detect text files and perform LF normalization
* text=auto

# Custom for Visual Studio
*.cs     diff=csharp

# Standard to msysgit
*.doc	 diff=astextplain
*.DOC	 diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot  diff=astextplain
*.DOT  diff=astextplain
*.pdf  diff=astextplain
*.PDF	 diff=astextplain
*.rtf	 diff=astextplain
*.RTF	 diff=astextplain


code:
# Compiled Lua sources
luac.out

# luarocks build files
*.src.rock
*.zip
*.tar.gz

# Object files
*.o
*.os
*.ko
*.obj
*.elf

# Precompiled Headers
*.gch
*.pch

# Libraries
*.lib
*.a
*.la
*.lo
*.def
*.exp

# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib

# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex

# =========================
# Operating System Files
# =========================

# Windows
# =========================

# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db

# Folder config file
Desktop.ini

# Recycle Bin used on file shares
$RECYCLE.BIN/

# Windows Installer files
*.cab
*.msi
*.msm
*.msp

# Windows shortcuts
*.lnk