## Roles core

Core Module - Roles - Factorio role system to manage custom permissions.

### Usage

    --- Using Role System (assignment):
    --When a map first starts you will want to define on mass all the players you expect to join and the roles to give them:
    Roles.override_player_roles{
        Cooldude2606 = {'Owner', 'Admin', 'Member'},
        NotCooldude2606 = {'Member'}
    }

    --Once the game is running you still want to be able to give role and remove them which is when you would use:
    Roles.assign_player(player, 'Admin', by_player_name) -- this will give the "Admin" role to the player
    Roles.unassign_player(player, {'Admin', 'Moderator'}, by_player_name) -- this will remove "Admin" and "Moderator" role in one go

    --- Using Role System (role testing):
    --To comparer two players you can comparer the index of they highest roles, can be used when you want to allow a "write" down type system:
    Roles.get_player_highest_role(playerOne).index < Roles.get_player_highest_role(playerTwo).index -- remember that less means a higher role

    --Listing all of a players roles can also be useful which is when you would want to use:
    Roles.get_player_roles(player) -- the return is an array that can be looped over however this is not in particular order

    --Finally you may want to test if a player has a certain role, flag or action allowed which is when you would use:
    Roles.player_has_role(player, 'Admin') -- you can provide a role name if you only want a name based system
    Roles.player_has_flag(player, 'is_donator') -- your roles can be grouped together with flags such as is_donator
    Roles.player_allowed(player, 'game modifiers') -- or you can have an action based system where each action is something the player can do

    --- Example Flag Define:
    --Flags can be used to group multiple roles and actions under one catch all, for example if you want a piece of code to only
    --be active for your donators then you would add a "is_donator" flag to all your donator roles and then in the code test if
    --a player has that tag present:

    -- give you donators a speed boost when they join; these functions aren't required but can be useful
    Roles.define_flag_trigger('is_donator', function(player, state)
        if state then
            player.character_running_speed_modifier = 1.5
        else
            player.character_running_speed_modifier = 1
        end
    end)

    -- then on all your donator roles you would add
    Roles.new_role('Donator')
    :set_flag('is_donator')

    -- and in your code you would test for
    if Roles.player_has_flag(player, 'is_donator') then
        -- some donator only code
    end

    --- Example Role Define:
    --You can't use a role system without any roles so first you must define your roles; each role has a minimum of a name with
    --the option for a shorthand:
    Roles.new_role('Administrator', 'Admin')

    --Next you will want to add any extras you want to have, such as a tag, colour, permission group or any custom flags:
    Roles.new_role('Administrator', 'Admin')
    :set_custom_tag('[Admin]')
    :set_custom_color('red') -- this can be {r=0, g=0, b=0} or a predefined value
    :set_permission_group('Staff') -- a second argument can be added if you have not used the custom permission group config
    :set_flag('is_admin')

    --You will then want to decide if you want to allow all actions, this should of course be used sparely:
    Roles.new_role('Administrator', 'Admin')
    ...extras...
    :set_allow_all()

    --If you don't do this want this as i would advise you do then you will want to define what the role can do; this comes with
    --an optional inheritance system if you like those sort of things in which case disallow may also be of some use to you:
    Roles.new_role('Administrator', 'Admin')
    ...extras...
    :set_parent('Moderator') -- the admin can do anything that a moderator can do
    :allow{ -- these actions can be anything just try to keep them without conflicts
        'command/kill',
        'gui/game settings'
    }

    --Here is what the finished admin role would look like:
    Roles.new_role('Administrator', 'Admin')
    :set_custom_tag('[Admin]')
    :set_custom_color('red')
    :set_permission_group('Staff')
    :set_flag('is_admin')
    :set_parent('Moderator')
    :allow{
        'command/kill',
        'gui/game settings'
    }

    --- Example System Define:
    --Once all roles are defined these steps must be done to ensure the system is ready to use, this includes setting a default
    --role, assigning a root (all permission) role that the server/system will use and the linear order that the roles fall into:

    Roles.set_default('Guest')
    Roles.set_root('System')

    Roles.define_role_order{
        'System',
        'Administrator',
        'Moderator',
        'Donator',
        'Guest'
    }

    --Just remember that in this example all these roles have not been defined; so make sure all your roles that are used are defined
    --before hand; a config file on load is useful for this to ensure that its loaded before the first player even joins.

### Dependencies

utils.game
utils.global
utils.event
expcore.permission_groups
expcore.async
utils.color_presets
expcore.common

### Getter

debug() | Returns a string which contains all roles in index order displaying all data for them
print_to_roles(roles, message) | Prints a message to all players in the given roles, may send duplicate message however factorio blocks spam
print_to_roles_higher(role, message) | Prints a message to all players who have the given role or one which is higher (excluding default)
print_to_roles_lower(role, message) | Prints a message to all players who have the given role or one which is lower (excluding default)
get_role_by_name(name) | Get a role for the given name
get_role_by_order(index) | Get a role with the given order index
get_role_from_any(any) | Gets a role from a name, index or role object (where it is just returned) nb: this function is used for the input for most outward facing functions
get_player_roles(player) | Gets all the roles of the given player, this will always contain the default role
get_player_highest_role(player) | Gets the highest role which the player has, can be used to compeer one player to another

### Assignment

assign_player(player, roles[, by_player_name=<server>][, skip_checks=false][, silent=false]) | Gives a player the given role(s) with an option to pass a by player name used in the log
unassign_player(player, roles[, by_player_name=<server>][, skip_checks=false][, silent=false]) | Removes a player from the given role(s) with an option to pass a by player name used in the log
override_player_roles([player_name], roles) | Overrides all player roles with the given table of roles, useful to mass set roles on game start

### Checks

player_has_role(player, search_role) | A test for weather a player has the given role
player_has_flag(player, flag_name) | A test for weather a player has the given flag true for at least one of they roles
player_allowed(player, action) | A test for weather a player has at least one role which is allowed the given action

### Definitions

define_role_order(order) | Used to set the role order, higher in the list is better, must be called at least once in config -- nb: function also re links parents due to expected position in the config file
define_flag_trigger(name, callback) | Defines a new trigger for when a tag is added or removed from a player
set_default(name) | Sets the default role which every player will have, this needs to be called at least once
set_root(name) | Sets the root role which will always have all permissions, any server actions act from this role
new_role(name[, short_hand=name]) | Defines a new role and returns the prototype to allow configuration

### Role Actions

Roles._prototype:set_allow_all([state=true]) | Sets the default allow state of the role, true will allow all actions
Roles._prototype:allow(actions) | Sets the allow actions for this role, actions in this list will be allowed for this role
Roles._prototype:disallow(actions) | Sets the disallow actions for this role, will prevent actions from being allowed regardless of inheritance
Roles._prototype:is_allowed(action) | Test for if a role is allowed the given action, mostly internal see Roles.player_allowed

### Role Flags

Roles._prototype:set_flag(name[, value=true]) | Sets the state of a flag for a role, flags can be used to apply effects to players
Roles._prototype:clear_flags() | Clears all flags from this role, individual flags can be removed with set_flag(name, false)
Roles._prototype:has_flag(name) | A test for if the role has a flag set

### Role Properties

Roles._prototype:set_custom_tag(tag) | Sets a custom player tag for the role, can be accessed by other code
Roles._prototype:set_custom_color(color) | Sets a custom colour for the role, can be accessed by other code
Roles._prototype:set_permission_group(name[, use_factorio_api=false]) | Sets the permission group for this role, players will be moved to the group of they highest role
Roles._prototype:set_parent(role) | Sets the parent for a role, any action not in allow or disallow will be looked for in its parents nb: this is a recursive action, and changing the allows and disallows will effect all children roles
Roles._prototype:set_auto_assign_condition(callback) | Sets an auto assign condition that is checked every 60 seconds, if true is returned then the player will receive the role nb: this is one way, failing false after already gaining the role will not revoke the role
Roles._prototype:get_auto_assign_condition() | Get the auto assign condition for this role, returns nil if no condition is set
Roles._prototype:set_block_auto_assign([state=true]) | Sets the role to not allow players to have auto assign effect them, useful to keep people locked to a role

### Role Players

Roles._prototype:add_player(player, skip_check, skip_event) | Adds a player to this role, players can have more than one role at a time, used internally see Roles.assign
Roles._prototype:remove_player(player, skip_check, skip_event) | Removes a player from this role, players can have more than one role at a time, used internally see Roles.unassign
Roles._prototype:get_players([online=nil]) | Returns an array of all the players who have this role, can be filtered by online status
Roles._prototype:print(message) | Will print a message to all players with this role

### Dependencies

# utils.game
# utils.global
# utils.event
# expcore.permission_groups
# expcore.async
# utils.color_presets
# expcore.common

### Getter

# debug()

  Returns a string which contains all roles in index order displaying all data for them

  Returns:
  - (string) the debug output string
  Usage:

      -- Print the debug string
      game.player.print(Roles.debug())

# print_to_roles(roles, message)

  Prints a message to all players in the given roles, may send duplicate message however factorio blocks spam

  Parameters:
  - roles : (table) table a of roles which to send the message to
  - message : (string) the message to send to the players
  Usage:

      -- Print a message to the given roles
      Roles.print_to_roles({'Administrator', 'Moderator'}, 'Hello, World!')

# print_to_roles_higher(role, message)

  Prints a message to all players who have the given role or one which is higher (excluding default)

  Parameters:
  - role : (string) the name of the role to send the message to
  - message : (string) the message to send to the players
  Usage:

      -- Print a message to the roles above this role, includes the given role
      Roles.print_to_roles_higher('Moderator', 'Hello, World!')

# print_to_roles_lower(role, message)

  Prints a message to all players who have the given role or one which is lower (excluding default)

  Parameters:
  - role : (string) the name of the role to send the message to
  - message : (string) the message to send to the players
  Usage:

      -- Print a message to the roles below this role, includes the given role
      Roles.print_to_roles_higher('Moderator', 'Hello, World!')

# get_role_by_name(name)

  Get a role for the given name

  Parameters:
  - name : (string) the name of the role to get
  Returns:
  - (Roles._prototype) the role with that name or nil
  Usage:

      -- Get a role by its name
      local role = Roles.get_role_by_name('Moderator')

# get_role_by_order(index)

  Get a role with the given order index

  Parameters:
  - index : (number) the place in the order list of the role to get
  Returns:
  - (Roles._prototype) the role with that index in the order list or nil
  Usage:

      -- Get a role by its index in the order list
      local role = Roles.get_role_by_name(2)

# get_role_from_any(any)

  Gets a role from a name, index or role object (where it is just returned) nb: this function is used for the input for most outward facing functions

  Parameters:
  - any : (number, string or table) the value used to find the role
  Returns:
  - (Roles._prototype) the role that was found or nil see above
  Usage:

      -- Get a role by its name or order
      local role = Roles.get_role_from_any('Moderator')

# get_player_roles(player)

  Gets all the roles of the given player, this will always contain the default role

  Parameters:
  - player : (LuaPlayer) the player to get the roles of
  Returns:
  - (table) a table where the values are the roles which the player has
  Usage:

      -- Get the roles that a player has
      local roles = Roles.get_player_roles(game.player)

# get_player_highest_role(player)

  Gets the highest role which the player has, can be used to compeer one player to another

  Parameters:
  - player : (LuaPlayer) the player to get the highest role of
  Returns:
  - (the) role with the highest order index which this player has
  Usage:

      -- Get the highest role that a player has
      local role = Roles.get_player_highest_role(game.player)

### Assignment

# assign_player(player, roles[, by_player_name=<server>][, skip_checks=false][, silent=false])

  Gives a player the given role(s) with an option to pass a by player name used in the log

  Parameters:
  - player : (LuaPlayer) the player that will be assigned the roles
  - roles : (table) table a of roles that the player will be given, can be one role and can be role names
  - by_player_name : (string) the name of the player that will be shown in the log (default: <server>)
  - skip_checks : (boolean) when true there will be no checks are done for if the player is valid (default: false)
  - silent : (boolean) when true there will be no game message printed (default: false)
  Usage:

      -- Assign a player to the Moderator role
      Roles.assign_player(game.player,  'Moderator')

      -- Assign a player to the Moderator role, even if the player has never been on the map
      Roles.assign_player('Cooldude2606',  'Moderator', nil, true)

# unassign_player(player, roles[, by_player_name=<server>][, skip_checks=false][, silent=false])

  Removes a player from the given role(s) with an option to pass a by player name used in the log

  Parameters:
  - player : (LuaPlayer) the player that will have the roles removed
  - roles : (table) table a of roles to be removed from the player, can be one role and can be role names
  - by_player_name : (string) the name of the player that will be shown in the logs (default: <server>)
  - skip_checks : (boolean) when true there will be no checks are done for if the player is valid (default: false)
  - silent : (boolean) when true there will be no game message printed (default: false)
  Usage:

      -- Unassign a player from the Moderator role
      Roles.unassign_player(game.player,  'Moderator')

      -- Unassign a player from the Moderator role, even if the player has never been on the map
      Roles.unassign_player('Cooldude2606',  'Moderator', nil, true)

# override_player_roles([player_name], roles)

  Overrides all player roles with the given table of roles, useful to mass set roles on game start

  Parameters:
  - player_name : (string) the player to set the roles for, if not given all roles are overriden (optional)
  - roles : (table) table a which is indexed by case sensitive player names and has the value of a table of role names
  Usage:

      -- Override the roles of a single player, other users are not effected
      Roles.override_player_roles('Cooldude2606', {'Moderator'})

      -- Override all existing roles, effects all users not just ones listed
      Roles.override_player_roles{
          ['Cooldude2606'] = {'Administrator', 'Moderator'},
          ['arty714'] = {'Administrator', 'Moderator'},
      }

### Checks

# player_has_role(player, search_role)

  A test for weather a player has the given role

  Parameters:
  - player : (LuaPlayer) the player to test the roles of
  - search_role : (string, number or table) a pointer to the role that is being searched for
  Returns:
  - (boolean) true if the player has the role, false otherwise, nil for errors
  Usage:

      -- Test if a player has a role
      local has_role = Roles.player_has_role(game.player, 'Moderator')

# player_has_flag(player, flag_name)

  A test for weather a player has the given flag true for at least one of they roles

  Parameters:
  - player : (LuaPlayer) the player to test the roles of
  - flag_name : (string) the name of the flag that is being looked for
  Returns:
  - (boolean) true if the player has at least one role which has the flag set to true, false otherwise, nil for errors
  Usage:

      -- Test if a player has a role
      local has_flag = Roles.player_has_flag(game.player, 'is_donator')

# player_allowed(player, action)

  A test for weather a player has at least one role which is allowed the given action

  Parameters:
  - player : (LuaPlayer) the player to test the roles of
  - action : (string) the name of the action that is being tested for
  Returns:
  - (boolean) true if the player has at least one role which is allowed this action, false otherwise, nil for errors
  Usage:

      -- Test if a player has a role
      local has_flag = Roles.player_has_flag(game.player, 'is_donator')

### Definitions

# define_role_order(order)

  Used to set the role order, higher in the list is better, must be called at least once in config -- nb: function also re links parents due to expected position in the config file

  Parameters:
  - order : (table) table a which is keyed only by numbers (start 1) and values are roles in order with highest first
  Usage:

      -- Define which roles are higher than others
      Roles.define_role_order{
          'System',
          'Administrator',
          'Moderator',
          'Donator',
          'Guest'
      }

# define_flag_trigger(name, callback)

  Defines a new trigger for when a tag is added or removed from a player

  Parameters:
  - name : (string) the name of the flag which the roles will have
  - callback : (function) the function that is called when roles are assigned
  Usage:

      -- Defining a flag trigger
      Roles.define_flag_trigger('is_donator', function(player, state)
          player.character_running_speed_modifier = state and 1.5 or 1
      end)

# set_default(name)

  Sets the default role which every player will have, this needs to be called at least once

  Parameters:
  - name : (string) the name of the default role
  Usage:

      -- Setting the default role
      Roles.set_default('Guest')

# set_root(name)

  Sets the root role which will always have all permissions, any server actions act from this role

  Parameters:
  - name : (string) the name of the root role
  Usage:

      -- Setting the root role
      Roles.set_root('System')

# new_role(name[, short_hand=name])

  Defines a new role and returns the prototype to allow configuration

  Parameters:
  - name : (string) the name of the new role, must be unique
  - short_hand : (string) the shortened version of the name (default: name)
  Returns:
  - (Roles._prototype) the start of the config chain for this role
  Usage:

      -- Defining a new role
      local role = Roles.new_role('Moderator', 'Mod')

### Role Actions

# Roles._prototype:set_allow_all([state=true])

  Sets the default allow state of the role, true will allow all actions

  Parameters:
  - state : (boolean) true will allow all actions (default: true)
  Returns:
  - (Roles._prototype) allows chaining
  Usage:

      -- Allow all actions for this role, useful for root like roles
      role:set_allow_all()

# Roles._prototype:allow(actions)

  Sets the allow actions for this role, actions in this list will be allowed for this role

  Parameters:
  - actions : (table) indexed with numbers and is an array of action names, order has no effect
  Returns:
  - (Roles._prototype) allows chaining
  Usage:

      -- Allow some actions for a role
      role:allow{
          'command/kill',
          'gui/game settings'
      }

# Roles._prototype:disallow(actions)

  Sets the disallow actions for this role, will prevent actions from being allowed regardless of inheritance

  Parameters:
  - actions : (table) indexed with numbers and is an array of action names, order has no effect
  Returns:
  - (Roles._prototype) allows chaining
  Usage:

      -- Disallow an action for a role, useful if inherit an action from a parent
      role:disallow{
          'command/kill',
          'gui/game settings'
      }

# Roles._prototype:is_allowed(action)

  Test for if a role is allowed the given action, mostly internal see Roles.player_allowed

  Parameters:
  - action : (string) the name of the action to test if it is allowed
  Returns:
  - (boolean) true if action is allowed, false otherwise
  Usage:

      -- Test if a role is allowed an action
      local allowed = role:is_allowed('command/kill')

### Role Flags

# Roles._prototype:set_flag(name[, value=true])

  Sets the state of a flag for a role, flags can be used to apply effects to players

  Parameters:
  - name : (string) the name of the flag to set the value of
  - value : (boolean) the state to set the flag to (default: true)
  Returns:
  - (Roles._prototype) allows chaining
  Usage:

      -- Set a flag for a role
      role:set_flag('is_admin')

# Roles._prototype:clear_flags()

  Clears all flags from this role, individual flags can be removed with set_flag(name, false)

  Returns:
  - (Roles._prototype) allows chaining
  Usage:

      -- Remove all flags from a role
      role:clear_flags()

# Roles._prototype:has_flag(name)

  A test for if the role has a flag set

  Parameters:
  - name : (string) the name of the flag to test for
  Returns:
  - (boolean) true if the flag is set, false otherwise
  Usage:

      -- Test if a role has a flag
      local has_flag = role:has_flag('is_admin')

### Role Properties

# Roles._prototype:set_custom_tag(tag)

  Sets a custom player tag for the role, can be accessed by other code

  Parameters:
  - tag : (string) the value that the tag will be
  Returns:
  - (Roles._prototype) allows chaining
  Usage:

      -- Set a custom tag for this role, other code is required to set the tag
      role:set_custom_tag('Mod')

# Roles._prototype:set_custom_color(color)

  Sets a custom colour for the role, can be accessed by other code

  Parameters:
  - color : (table) ?string|table can either be and rgb colour or the name of a colour defined in the presets
  Returns:
  - (Roles._prototype) allows chaining
  Usage:

      -- Set a custom colour for this role, other code is required to use this value
      role:set_custom_color{ r=255, g=100, b=100}

# Roles._prototype:set_permission_group(name[, use_factorio_api=false])

  Sets the permission group for this role, players will be moved to the group of they highest role

  Parameters:
  - name : (string) the name of the permission group to have players moved to
  - use_factorio_api : (boolean) when true the custom permission group module is ignored (default: false)
  Returns:
  - (Roles._prototype) allows chaining
  Usage:

      -- Set the permission group for this role, see permission_groups.lua
      role:set_permission_group('Admin')

# Roles._prototype:set_parent(role)

  Sets the parent for a role, any action not in allow or disallow will be looked for in its parents nb: this is a recursive action, and changing the allows and disallows will effect all children roles

  Parameters:
  - role : (string) the name of the role that will be the parent; has imminent effect if role is already defined
  Returns:
  - (Roles._prototype) allows chaining
  Usage:

      -- Set the parent for this role to inherit all actions allowed
      role:set_parent('Guest')

# Roles._prototype:set_auto_assign_condition(callback)

  Sets an auto assign condition that is checked every 60 seconds, if true is returned then the player will receive the role nb: this is one way, failing false after already gaining the role will not revoke the role

  Parameters:
  - callback : (function) receives only one param which is player to assign, return true to assign the player
  Returns:
  - (Roles._prototype) allows chaining
  Usage:

      -- Give this role to a user if there are admin, ran every 60 seconds
      role:set_auto_assign_condition(function(player)
          return player.admin
      end)

# Roles._prototype:get_auto_assign_condition()

  Get the auto assign condition for this role, returns nil if no condition is set

  Returns:
  - (function) The callback which was assigned as the auto assign condition
  Usage:

      -- Give this role to a user if there are admin, ran every 60 seconds
      local condition = role:get_auto_assign_condition()

# Roles._prototype:set_block_auto_assign([state=true])

  Sets the role to not allow players to have auto assign effect them, useful to keep people locked to a role

  Parameters:
  - state : (boolean) when true the players with this role will not be auto assigned to other roles (default: true)
  Returns:
  - (Roles._prototype) allows chaining
  Usage:

      -- Make a role stop players from being auto assigned to other roles
      role:set_block_auto_assign()

### Role Players

# Roles._prototype:add_player(player, skip_check, skip_event)

  Adds a player to this role, players can have more than one role at a time, used internally see Roles.assign

  Parameters:
  - player : (LuaPlayer) the player that will be given this role
  - skip_check : (boolean) when true player will be taken as the player name (use when player has not yet joined)
  - skip_event : (boolean) when true the event emit will be skipped, this is used internally with Roles.assign
  Returns:
  - (boolean) true if the player was added successfully
  Usage:

      -- Assign a player to this role
      role:add_player(game.player)

# Roles._prototype:remove_player(player, skip_check, skip_event)

  Removes a player from this role, players can have more than one role at a time, used internally see Roles.unassign

  Parameters:
  - player : (LuaPlayer) the player that will lose this role
  - skip_check : (boolean) when true player will be taken as the player name (use when player has not yet joined)
  - skip_event : (boolean) when true the event emit will be skipped, this is used internally with Roles.unassign
  Returns:
  - (boolean) true if the player was removed successfully
  Usage:

      -- Unassign a player from this role
      role:remove_player(game.player)

# Roles._prototype:get_players([online=nil])

  Returns an array of all the players who have this role, can be filtered by online status

  Parameters:
  - online : (boolean) when given will filter by this online state, nil will return all players (default: nil)
  Returns:
  - (table) all the players who have this role, indexed order is meaningless
  Usage:

      -- Get all the players with this role
      local players = role:get_players()

      -- Get all online players with this role
      local players = role:get_players(true)

# Roles._prototype:print(message)

  Will print a message to all players with this role

  Parameters:
  - message : (string) the message that will be printed to the players
  Returns:
  - (number) the number of players who received the message
  Usage:

      -- Print a message to all players with this role
      role:print('Hello, World!')