Merge branch 'dev' into feature/entity-alert

This commit is contained in:
Cooldude2606
2021-04-26 00:21:48 +01:00
committed by GitHub
127 changed files with 4579 additions and 629 deletions

View File

@@ -0,0 +1,66 @@
--- Kicks players when all players on the server are afk
-- @addon afk-kick
local Event = require 'utils.event' --- @dep utils.event
local Global = require 'utils.global' --- @dep utils.global
local config = require 'config.afk_kick' --- @dep config.afk_kick
local Async = require 'expcore.async' --- @dep expcore.async
--- Optional roles require
local Roles
if config.active_role then
Roles = require 'expcore.roles'
end
--- Globals
local primitives = { last_active = 0 }
Global.register(primitives, function(tbl)
primitives = tbl
end)
--- Kicks an afk player, used to add a delay so the gui has time to appear
local kick_player =
Async.register(function(player)
if game.tick - primitives.last_active < config.kick_time then return end -- Safety Catch
game.kick_player(player, 'AFK while no active players on the server')
end)
--- Check for an active player every update_time number of ticks
Event.on_nth_tick(config.update_time, function()
-- Check for active players
for _, player in ipairs(game.connected_players) do
if player.afk_time < config.afk_time
or config.admin_as_active and config.player.admin
or config.trust_as_active and player.online_time > config.trust_time
or config.active_role and Roles.player_has_role(player, config.active_role) then
-- Active player was found
primitives.last_active = game.tick
return
end
end
-- No active player was found, check if players should be kicked
if game.tick - primitives.last_active < config.kick_time then return end
-- Kick time exceeded, kick all players
for _, player in ipairs(game.connected_players) do
-- Add a frame to say why the player was kicked
local res = player.display_resolution
local uis = player.display_scale
player.gui.screen.add{
type = 'frame',
name = 'afk-kick',
caption = {'afk-kick.message'},
}.location = { x=res.width*(0.5 - 0.11*uis), y=res.height*(0.5 - 0.14*uis) }
-- Kick the player, some delay needed because network delay
Async.wait(10, kick_player, player)
end
end)
--- Remove the screen gui if it is present
Event.add(defines.events.on_player_joined_game, function(event)
local player = game.get_player(event.player_index)
local frame = player.gui.screen["afk-kick"]
if frame and frame.valid then frame.destroy() end
end)

View File

@@ -59,7 +59,7 @@ local function emit_event(args)
}
local new_value, inline = value:gsub('<inline>', '', 1)
if inline then
if inline > 0 then
field.value = new_value
field.inline = true
end
@@ -102,6 +102,7 @@ if config.player_reports then
color=Colors.yellow,
['Player']='<inline>'..player_name,
['By']='<inline>'..by_player_name,
['Report Count']='<inline>'..Reports.count_reports(player_name),
['Reason']=event.reason
}
end)
@@ -114,7 +115,7 @@ if config.player_reports then
color=Colors.green,
['Player']='<inline>'..player_name,
['By']='<inline>'..event.removed_by_name,
['Amount']='<inline>'..event.batch_count
['Report Count']='<inline>'..event.batch_count
}
end)
end
@@ -124,12 +125,14 @@ if config.player_warnings then
local Warnings = require 'modules.control.warnings' --- @dep modules.control.warnings
Event.add(Warnings.events.on_warning_added, function(event)
local player_name, by_player_name = get_player_name(event)
local player = game.get_player(player_name)
emit_event{
title='Warning',
description='A player has been given a warning',
color=Colors.yellow,
['Player']='<inline>'..player_name,
['By']='<inline>'..by_player_name,
['Warning Count']='<inline>'..Warnings.count_warnings(player),
['Reason']=event.reason
}
end)
@@ -142,7 +145,7 @@ if config.player_warnings then
color=Colors.green,
['Player']='<inline>'..player_name,
['By']='<inline>'..event.removed_by_name,
['Amount']='<inline>'..event.batch_count
['Warning Count']='<inline>'..event.batch_count
}
end)
end

View File

@@ -0,0 +1,25 @@
--- When a player is reported, the player is automatically jailed if the combined playtime of the reporters exceeds the reported player
-- @addon report-jail
local Event = require 'utils.event' ---@dep utils.event
local Jail = require 'modules.control.jail' ---@dep modules.control.jail
local Reports = require 'modules.control.reports' --- @dep modules.control.reports
local format_chat_player_name = _C.format_chat_player_name --- @dep expcore.common
--- Returns the playtime of the reporter. Used when calculating the total playtime of all reporters
local function reporter_playtime(_, by_player_name, _)
local player = game.get_player(by_player_name)
if player == nil then return 0 end
return player.online_time
end
--- Tests the combined playtime of all reporters against the reported player
Event.add(Reports.events.on_player_reported, function(event)
local player = game.get_player(event.player_index)
local total_playtime = Reports.count_reports(player, reporter_playtime)
if total_playtime < player.online_time*1.5 then return end
-- Combined playtime is greater than 150% of the reported's playtime
local player_name_color = format_chat_player_name(player)
Jail.jail_player(player, '<reports>', 'Reported by too many players, please wait for a moderator.')
game.print{'report-jail.jail', player_name_color}
end)

View File

@@ -4,16 +4,23 @@
local Global = require 'utils.global' --- @dep utils.global
local Event = require 'utils.event' --- @dep utils.event
local config = require 'config.spawn_area' --- @dep config.spawn_area
local tiles = config.tiles
local entities = config.entities
local belts = config.afk_belts.locations
local turrets = config.infinite_ammo_turrets.locations
local turrets = config.turrets.locations
Global.register(turrets, function(tbl)
turrets = tbl
end)
-- returns the Spawn force or creates it
-- Apply an offset to a LuaPosition
local function apply_offset(position, offset)
return { x = position.x + (offset.x or offset[1]), y = position.y + (offset.y or offset[2]) }
end
-- Apply the offset to the turrets default position
for _, turret in ipairs(turrets) do
turret.position = apply_offset(turret.position, config.turrets.offset)
end
-- Get or create the force used for entities in spawn
local function get_spawn_force()
local force = game.forces['Spawn']
if force and force.valid then return force end
@@ -23,7 +30,7 @@ local function get_spawn_force()
return force
end
-- protects and entity so players cant do anything to it
-- Protects an entity and sets its force to the spawn force
local function protect_entity(entity, set_force)
if entity and entity.valid then
entity.destructible = false
@@ -35,113 +42,133 @@ local function protect_entity(entity, set_force)
end
end
-- handles the infinite ammo turrets
-- Will spawn all infinite ammo turrets and keep them refilled
local function spawn_turrets()
if config.infinite_ammo_turrets.enabled then
for _, turret_pos in pairs(turrets) do
local surface = game.surfaces[turret_pos.surface]
local pos = turret_pos.position
local turret = surface.find_entity('gun-turret', pos)
-- Makes a new turret if it is not found
if not turret or not turret.valid then
turret = surface.create_entity{name='gun-turret', position=pos, force='Spawn'}
protect_entity(turret, true)
end
-- adds ammo to the turret
local inv = turret.get_inventory(defines.inventory.turret_ammo)
if inv.can_insert{name=config.infinite_ammo_turrets.ammo_type, count=10} then
inv.insert{name=config.infinite_ammo_turrets.ammo_type, count=10}
end
for _, turret_pos in pairs(turrets) do
local surface = game.surfaces[turret_pos.surface]
local pos = turret_pos.position
local turret = surface.find_entity('gun-turret', pos)
-- Makes a new turret if it is not found
if not turret or not turret.valid then
turret = surface.create_entity{name='gun-turret', position=pos, force='Spawn'}
protect_entity(turret, true)
end
-- Adds ammo to the turret
local inv = turret.get_inventory(defines.inventory.turret_ammo)
if inv.can_insert{name=config.turrets.ammo_type, count=10} then
inv.insert{name=config.turrets.ammo_type, count=10}
end
end
end
-- makes a 2x2 afk belt where set in config
-- Makes a 2x2 afk belt at the locations in the config
local function spawn_belts(surface, position)
position = apply_offset(position, config.afk_belts.offset)
local belt_type = config.afk_belts.belt_type
local belt_details = {{-0.5, -0.5, 2}, {0.5, -0.5, 4}, {-0.5, 0.5, 0}, {0.5, 0.5, 6}} -- x, y,dir
for _, belt_set in pairs(belts) do
local o = position
local p = belt_set
for _, belt_set in pairs(config.afk_belts.locations) do
local set_position = apply_offset(position, belt_set)
for _, belt in pairs(belt_details) do
local pos = {x=o.x+p.x+belt[1], y=o.y+p.y+belt[2]}
local belt_entity = surface.create_entity{name='transport-belt', position=pos, force='neutral', direction=belt[3]}
protect_entity(belt_entity)
local pos = apply_offset(set_position, belt)
local belt_entity = surface.create_entity{name=belt_type, position=pos, force='neutral', direction=belt[3]}
if config.afk_belts.protected then protect_entity(belt_entity) end
end
end
end
-- generates an area with no water and removes entities in the decon area
local function spawn_base(surface, position)
local dr = config.corrections.deconstruction_radius
local dr2 = dr^2
local dtile = config.corrections.deconstruction_tile
local pr = config.corrections.pattern_radius
local pr2 = pr^2
local ptile = surface.get_tile(position).name
if ptile == 'deepwater' or ptile == 'water' then ptile = 'grass-1' end
-- Generates extra tiles in a set pattern as defined in the config
local function spawn_pattern(surface, position)
position = apply_offset(position, config.pattern.offset)
local tiles_to_make = {}
for x = -pr, pr do -- loop over x
local x2 = x^2
for y = -pr, pr do -- loop over y
local y2 = y^2
local prod = x2+y2
local p = {x=position.x+x, y=position.y+y}
if prod < dr2 then
-- if it is inside the decon radius
table.insert(tiles_to_make, {name=dtile, position=p})
local entities_to_remove = surface.find_entities_filtered{area={{p.x-1, p.y-1}, {p.x, p.y}}}
for _, entity in pairs(entities_to_remove) do
if entity.name ~= 'character' then entity.destroy() end
end
elseif prod < pr2 then
-- if it is inside the pattern radius
table.insert(tiles_to_make, {name=ptile, position=p})
local pattern_tile = config.pattern.pattern_tile
for _, tile in pairs(config.pattern.locations) do
table.insert(tiles_to_make, {name=pattern_tile, position=apply_offset(position, tile)})
end
surface.set_tiles(tiles_to_make)
end
-- Generates extra water as defined in the config
local function spawn_water(surface, position)
position = apply_offset(position, config.water.offset)
local tiles_to_make = {}
local water_tile = config.water.water_tile
for _, tile in pairs(config.water.locations) do
table.insert(tiles_to_make, {name=water_tile, position=apply_offset(position, tile)})
end
surface.set_tiles(tiles_to_make)
end
-- Generates the entities that are in the config
local function spawn_entities(surface, position)
position = apply_offset(position, config.entities.offset)
for _, entity in pairs(config.entities.locations) do
local pos = apply_offset(position, { x=entity[2], y=entity[3] })
entity = surface.create_entity{name=entity[1], position=pos, force='neutral'}
if config.entities.protected then protect_entity(entity) end
entity.operable = config.entities.operable
end
end
-- Generates an area with no water or entities, no water area is larger
local function spawn_area(surface, position)
local dr = config.spawn_area.deconstruction_radius
local dr2 = dr^2
local decon_tile = config.spawn_area.deconstruction_tile
local fr = config.spawn_area.landfill_radius
local fr2 = fr^2
local fill_tile = surface.get_tile(position).name
-- Make sure a non water tile is used for each tile
if surface.get_tile(position).collides_with('player-layer') then fill_tile = 'landfill' end
if decon_tile == nil then decon_tile = fill_tile end
local tiles_to_make = {}
for x = -fr, fr do -- loop over x
local x2 = (x+0.5)^2
for y = -fr, fr do -- loop over y
local y2 = (y+0.5)^2
local dst = x2+y2
local pos = {x=position.x+x, y=position.y+y}
if dst < dr2 then
-- If it is inside the decon radius always set the tile
table.insert(tiles_to_make, {name=decon_tile, position=pos})
elseif dst < fr2 and surface.get_tile(pos).collides_with('player-layer') then
-- If it is inside the fill radius only set the tile if it is water
table.insert(tiles_to_make, {name=fill_tile, position=pos})
end
end
end
-- Remove entities then set the tiles
local entities_to_remove = surface.find_entities_filtered{position=position, radius=dr, name='character', invert=true}
for _, entity in pairs(entities_to_remove) do entity.destroy() end
surface.set_tiles(tiles_to_make)
end
-- generates the pattern that is in the config
local function spawn_pattern(surface, position)
local tiles_to_make = {}
local ptile = config.corrections.pattern_tile
local o = config.corrections.offset
local p = {x=position.x+o.x, y=position.y+o.y}
for _, tile in pairs(tiles) do
table.insert(tiles_to_make, {name=ptile, position={tile[1]+p.x, tile[2]+p.y}})
end
surface.set_tiles(tiles_to_make)
-- Only add a event handler if the turrets are enabled
if config.turrets.enabled then
Event.on_nth_tick(config.turrets.refill_time, function()
if game.tick < 10 then return end
spawn_turrets()
end)
end
-- generates the entities that are in the config
local function spawn_entities(surface, position)
local o = config.corrections.offset
local p = {x=position.x+o.x, y=position.y+o.y}
for _, entity in pairs(entities) do
entity = surface.create_entity{name=entity[1], position={entity[2]+p.x, entity[3]+p.y}, force='neutral'}
protect_entity(entity)
entity.operable = true
end
end
local refill_time = 60*60*5 -- 5 minutes
Event.on_nth_tick(refill_time, function()
if game.tick < 10 then return end
spawn_turrets()
end)
-- When the first player joins create the spawn area
Event.add(defines.events.on_player_created, function(event)
if event.player_index ~= 1 then return end
local player = game.players[event.player_index]
local p = {x=0, y=0}
local s = player.surface
spawn_base(s, p)
spawn_pattern(s, p)
get_spawn_force()
spawn_entities(s, p)
spawn_belts(s, p)
spawn_turrets()
spawn_area(s, p)
if config.pattern.enabled then spawn_pattern(s, p) end
if config.water.enabled then spawn_water(s, p) end
if config.afk_belts.enabled then spawn_belts(s, p) end
if config.turrets.enabled then spawn_turrets() end
if config.entities.enabled then spawn_entities(s, p) end
player.teleport(p, s)
end)

View File

@@ -0,0 +1,88 @@
--[[-- Commands Module - Admin Markers
- Adds a command that creates map markers which can only be edited by admins
@commands Admin-Markers
]]
local Commands = require 'expcore.commands' --- @dep expcore.commands
local Global = require 'utils.global' --- @dep utils.global
local Event = require 'utils.event' --- @dep utils.event
local admins = {} -- Stores all players in admin marker mode
local markers = {} -- Stores all admin markers
--- Global variables
Global.register({
admins = admins,
markers = markers
}, function(tbl)
admins = tbl.admins
markers = tbl.markers
end)
--- Toggle admin marker mode, can only be applied to yourself
-- @command admin-marker
Commands.new_command('admin-marker', 'Toggles admin marker mode, new markers can only be edited by admins')
:set_flag('admin_only')
:add_alias('am', 'admin-markers')
:register(function(player)
if admins[player.name] then
-- Exit admin mode
admins[player.name] = nil
return Commands.success{'expcom-admin-marker.exit'}
else
-- Enter admin mode
admins[player.name] = true
return Commands.success{'expcom-admin-marker.enter'}
end
end)
--- Listen for new map markers being added, add admin marker if done by player in admin mode
Event.add(defines.events.on_chart_tag_added, function(event)
if not event.player_index then return end
local player = game.get_player(event.player_index)
if not admins[player.name] then return end
local tag = event.tag
markers[tag.force.name..tag.tag_number] = true
Commands.print({'expcom-admin-marker.place'}, nil, player)
end)
--- Listen for players leaving the game, leave admin mode to avoid unexpected admin markers
Event.add(defines.events.on_player_left_game, function(event)
if not event.player_index then return end
local player = game.get_player(event.player_index)
admins[player.name] = nil
end)
--- Listen for tags being removed or edited, maintain tags edited by non admins
local function maintain_tag(event)
local tag = event.tag
if not event.player_index then return end
if not markers[tag.force.name..tag.tag_number] then return end
local player = game.get_player(event.player_index)
if player.admin then
-- Player is admin, tell them it was an admin marker
Commands.print({'expcom-admin-marker.edit'}, nil, player)
elseif event.name == defines.events.on_chart_tag_modified then
-- Tag was modified, revert the changes
tag.text = event.old_text
tag.last_user = event.old_player
if event.old_icon then tag.icon = event.old_icon end
player.play_sound{path='utility/wire_pickup'}
Commands.print({'expcom-admin-marker.revert'}, nil, player)
else
-- Tag was removed, remake the tag
player.play_sound{path='utility/wire_pickup'}
Commands.print({'expcom-admin-marker.revert'}, 'orange_red', player)
local new_tag = tag.force.add_chart_tag(tag.surface, {
last_user = tag.last_user,
position = tag.position,
icon = tag.icon,
text = tag.text,
})
markers[tag.force.name..tag.tag_number] = nil
markers[new_tag.force.name..new_tag.tag_number] = true
end
end
Event.add(defines.events.on_chart_tag_modified, maintain_tag)
Event.add(defines.events.on_chart_tag_removed, maintain_tag)

View File

@@ -9,6 +9,15 @@ local Reports = require 'modules.control.reports' --- @dep modules.control.repor
local format_chat_player_name = _C.format_chat_player_name--- @dep expcore.common
require 'config.expcore.command_general_parse'
--- Print a message to all players who match the value of admin
local function print_to_players(admin, message)
for _, player in ipairs(game.connected_players) do
if player.admin == admin then
player.print(message)
end
end
end
--- Reports a player and notifies moderators
-- @command report
-- @tparam LuaPlayer player the player to report, some players are immune
@@ -19,6 +28,8 @@ Commands.new_command('report', 'Reports a player and notifies moderators')
if not input then return end
if Roles.player_has_flag(input, 'report-immune') then
return reject{'expcom-report.player-immune'}
elseif player == input then
return reject{'expcom-report.self-report'}
else
return input
end
@@ -30,8 +41,8 @@ end)
local action_player_name_color = format_chat_player_name(action_player)
local by_player_name_color = format_chat_player_name(player)
if Reports.report_player(action_player, player.name, reason) then
game.print{'expcom-report.non-admin', action_player_name_color, reason}
Roles.print_to_roles_higher('Trainee', {'expcom-report.admin', action_player_name_color, by_player_name_color, reason})
print_to_players(false, {'expcom-report.non-admin', action_player_name_color, reason})
print_to_players(true, {'expcom-report.admin', action_player_name_color, by_player_name_color, reason})
else
return Commands.error{'expcom-report.already-reported'}
end

167
modules/commands/search.lua Normal file
View File

@@ -0,0 +1,167 @@
--[[-- Commands Module - Inventory Search
- Adds commands that will search all players inventories for an item
@commands InventorySearch
]]
local Commands = require 'expcore.commands' --- @dep expcore.commands
local format_number = require('util').format_number --- @dep util
local format_chat_player_name = _C.format_chat_player_name --- @dep expcore.common
local format_time = _C.format_time
require 'config.expcore.command_general_parse'
--- Input parse for items by name
local function item_parse(input, _, reject)
if input == nil then return end
local lower_input = input:lower():gsub(' ', '-')
-- Simple Case - internal name is given
local item = game.item_prototypes[lower_input]
if item then return item end
-- Second Case - rich text is given
local item_name = input:match('%[item=([0-9a-z-]+)%]')
item = game.item_prototypes[item_name]
if item then return item end
-- No item found, we do not attempt to search all prototypes as this will be expensive
return reject{'expcom-inv-search.reject-item', lower_input}
end
--- Search all players for this item
local function search_players(players, item)
local head = 1
local found = {}
-- Check the item count of all players
for _, player in pairs(players) do
local item_count = player.get_item_count(item.name)
if item_count > 0 then
-- Add the player to the array as they have the item
found[head] = { player=player, count=item_count, online_time=player.online_time }
head = head + 1
end
end
return found
end
--- Custom sort function which only retains 5 greatest values
local function sort_players(players, func)
local sorted = {}
local values = {}
local threshold = nil
-- Loop over all provided players
for index, player in ipairs(players) do
local value = func(player)
-- Check if the item will make the top 5 elements
if value > threshold or index <= 5 then
local inserted = false
values[player] = value
-- Find where in the top 5 to insert the element
for next_index, next_player in ipairs(sorted) do
if value > values[next_player] then
table.insert(sorted, next_index, player)
inserted = true
break
end
end
-- Insert the element, this can only be called when index <= 5
if not inserted then
sorted[#sorted+1] = player
end
-- Update the threshold
if sorted[6] then
threshold = values[sorted[5]]
values[sorted[6]] = nil
sorted[6] = nil
else
threshold = values[sorted[#sorted]]
end
end
end
return sorted
end
--- Display to the player the top players which were found
local function display_players(player, players, item)
player.print{'expcom-inv-search.results-heading', item.name}
for index, data in ipairs(players) do
local player_name_color = format_chat_player_name(data.player)
local amount = format_number(data.count)
local time = format_time(data.online_time)
player.print{'expcom-inv-search.results-item', index, player_name_color, amount, time}
end
end
--- Return the amount of an item a player has
local function amount_sort(data)
return data.count
end
--- Get a list of players sorted by the quantity of an item in their inventory
-- @command search-amount
-- @tparam LuaItemPrototype item The item to search for in players inventories
Commands.new_command('search-amount', 'Display players sorted by the quantity of an item held')
:add_alias('ia')
:add_param('item', false, item_parse)
:enable_auto_concat()
:register(function(player, item)
local players = search_players(game.players, item)
if #players == 0 then return {'expcom-inv-search.results-none', item.name} end
local top_players = sort_players(players, amount_sort)
display_players(player, top_players, item)
end)
--- Return the index of the player, higher means they joined more recently
local function recent_sort(data)
return data.player.index
end
--- Get a list of players who have the given item, sorted by how recently they joined
-- @command search-recent
-- @tparam LuaItemPrototype item The item to search for in players inventories
Commands.new_command('search-recent', 'Display players who hold an item sorted by join time')
:add_alias('ir')
:add_param('item', false, item_parse)
:enable_auto_concat()
:register(function(player, item)
local players = search_players(game.players, item)
if #players == 0 then return {'expcom-inv-search.results-none', item.name} end
local top_players = sort_players(players, recent_sort)
display_players(player, top_players, item)
end)
--- Return the the amount of an item a player has divided by their playtime
local function combined_sort(data)
return data.count/data.online_time
end
--- Get a list of players sorted by quantity held and play time
-- @command search
-- @tparam LuaItemPrototype item The item to search for in players inventories
Commands.new_command('search', 'Display players sorted by the quantity of an item held and playtime')
:add_alias('i')
:add_param('item', false, item_parse)
:enable_auto_concat()
:register(function(player, item)
local players = search_players(game.players, item)
if #players == 0 then return {'expcom-inv-search.results-none', item.name} end
local top_players = sort_players(players, combined_sort)
display_players(player, top_players, item)
end)
--- Get a list of online players sorted by quantity held and play time
-- @command search-online
-- @tparam LuaItemPrototype item The item to search for in players inventories
Commands.new_command('search-online', 'Display online players sorted by the quantity of an item held and playtime')
:add_alias('io')
:add_param('item', false, item_parse)
:enable_auto_concat()
:register(function(player, item)
local players = search_players(game.connected_players, item)
if #players == 0 then return {'expcom-inv-search.results-none', item.name} end
local top_players = sort_players(players, combined_sort)
display_players(player, top_players, item)
end)

View File

@@ -0,0 +1,33 @@
--[[-- Commands Module - Spectate
- Adds commands relating to spectate and follow
@commands Spectate
]]
local Spectate = require 'modules.control.spectate' --- @dep modules.control.spectate
local Commands = require 'expcore.commands' --- @dep expcore.commands
require 'config.expcore.command_general_parse'
--- Toggles spectator mode for the caller
-- @command spectate
Commands.new_command('spectate', 'Toggles spectator mode')
:register(function(player)
if Spectate.is_spectating(player) then
Spectate.stop_spectate(player)
else
Spectate.start_spectate(player)
end
end)
--- Enters follow mode for the caller, following the given player.
-- @command follow
-- @tparam LuaPlayer player The player that will be followed
Commands.new_command('follow', 'Start following a player in spectator')
:add_alias('f')
:add_param('player', false, 'player-online')
:register(function(player, action_player)
if player == action_player then
return Commands.error{'expcom-spectate.follow-self'}
else
Spectate.start_follow(player, action_player)
end
end)

View File

@@ -132,7 +132,7 @@ function Reports.report_player(player, by_player_name, reason)
if not player then return end
local player_name = player.name
reason = reason or 'Non given.'
if reason == nil or not reason:find("/S") then reason = 'No reason given' end
local reports = user_reports[player_name]
if not reports then

View File

@@ -0,0 +1,168 @@
local Event = require 'utils.event' --- @dep utils.event
local Global = require 'utils.global' --- @dep utils.global
local Gui = require 'expcore.gui' --- @dep expcore.gui
----- Locals -----
local follow_label -- Gui constructor
local following = {}
local spectating = {}
local Public = {}
----- Global data -----
Global.register({
following = following,
spectating = spectating
}, function(tbl)
following = tbl.following
spectating = tbl.spectating
end)
----- Public Functions -----
--- Test if a player is in spectator mode
-- @tparam LuaPlayer player The player to test the controller type of
-- @treturn boolean Returns true if the player is in spectator mode
function Public.is_spectating(player)
assert(player and player.valid, 'Invalid player given to follower')
return player.controller_type == defines.controllers.spectator
end
--- Puts a player into spectator mode while maintaining an association to their character
-- @tparam LuaPlayer player The player that will be placed into spectator mode
-- @treturn boolean Returns false if the player was already in spectator mode
function Public.start_spectate(player)
assert(player and player.valid, 'Invalid player given to follower')
if spectating[player.index] or not player.character then return false end
local character = player.character
player.set_controller{ type = defines.controllers.spectator }
player.associate_character(character)
spectating[player.index] = character
return true
end
--- Return a player from spectator mode back to their character, if their character was killed then respawn them
-- @tparam LuaPlayer player The player that will leave spectator mode
function Public.stop_spectate(player)
assert(player and player.valid, 'Invalid player given to follower')
local character = spectating[player.index]
spectating[player.index] = nil
if character and character.valid then
local opened = player.opened
player.teleport(character.position, character.surface)
player.set_controller{ type = defines.controllers.character, character = character }
if opened then player.opened = opened end -- Maintain opened after controller change
else
player.ticks_to_respawn = 300
end
end
--- Test if a player is in follow mode
-- @tparam LuaPlayer player The player to test the follow mode of
-- @treturn boolean Returns true if the player is in follow mode
function Public.is_following(player)
assert(player and player.valid, 'Invalid player given to follower')
return following[player.index] ~= nil
end
--- Puts a player into spectator mode and follows an entity as it moves
-- @tparam LuaPlayer player The player that will follow the entity
-- @tparam ?LuaPlayer|LuaEntity entity The player or entity that will be followed
function Public.start_follow(player, entity)
assert(player and player.valid, 'Invalid player given to follower')
assert(entity and entity.valid, 'Invalid entity given to follower')
local spectate = Public.start_spectate(player)
player.close_map()
follow_label(player.gui.screen, entity)
player.teleport(entity.position, entity.surface)
following[player.index] = { player, entity, entity.position, spectate }
end
--- Returns camera control to the player, will return a player to their character if start_follow placed them into spectator mode
-- @tparam LuaPlayer player The player that will regain control of their camera
function Public.stop_follow(player)
assert(player and player.valid, 'Invalid player given to follower')
if following[player.index] and following[player.index][4] then Public.stop_spectate(player) end
Gui.destroy_if_valid(player.gui.screen[follow_label.name])
following[player.index] = nil
end
--- Returns camera control to all players, will return a player to their character if start_follow placed them into spectator mode
function Public.stop_all()
for key, data in pairs(following) do
Public.stop_follow(data[1])
end
end
----- Gui -----
--- Label used to show that the player is following, also used to allow esc to stop following
-- @element follow_label
follow_label =
Gui.element(function(event_trigger, parent, target)
Gui.destroy_if_valid(parent[event_trigger])
local label = parent.add{
name = event_trigger,
type = 'label',
style = 'heading_1_label',
caption = 'Following '..target.name..'.\nClick here or press esc to stop following.'
}
local player = Gui.get_player_from_element(parent)
local res = player.display_resolution
label.location = {0, res.height-150}
label.style.width = res.width
label.style.horizontal_align = 'center'
player.opened = label
return label
end)
:on_click(Public.stop_follow)
:on_close(function(player)
-- Don't call set_controller during on_close as it invalidates the controller
-- Setting an invalid position (as to not equal their current) will call stop_follow on the next tick
following[player.index][3] = {}
end)
----- Events -----
--- Updates the location of the player as well as doing some sanity checks
-- @tparam LuaPlayer player The player to update the position of
-- @tparam ?LuaPlayer|LuaEntity entity The player or entity being followed
local function update_player_location(player, entity, old_position)
if player.character or not entity.valid then
Public.stop_follow(player)
elseif player.position.x ~= old_position.x or player.position.y ~= old_position.y then
Public.stop_follow(player)
else
player.teleport(entity.position, entity.surface)
end
end
--- Updates the locations of all players currently following something
local function update_all()
for _, data in pairs(following) do
update_player_location(data[1], data[2], data[3])
data[3] = data[1].position
end
end
-- Update the location of all players each tick
Event.add(defines.events.on_tick, update_all)
-- Check for player leaving
Event.add(defines.events.on_pre_player_left_game, function(event)
local player = game.players[event.player_index]
Public.stop_follow(player)
for _, data in pairs(following) do
if data[2] == player then
Public.stop_follow(data[1])
end
end
end)
----- Module Return -----
return Public

View File

@@ -72,9 +72,10 @@ Gui.element{
}
:style(Gui.sprite_style(30, -1, { left_margin = -2, right_margin = -1 }))
:on_click(function(player, element)
local reason = element.parent.entry.text or 'Non Given'
local reason = element.parent.entry.text
local action_name = SelectedAction:get(player)
local reason_callback = config.buttons[action_name].reason_callback
if reason == nil or not reason:find("/S") then reason = 'no reason given' end
reason_callback(player, reason)
SelectedPlayer:remove(player)
SelectedAction:remove(player)