Merge branch 'explosivegaming:main' into aperx2

This commit is contained in:
2026-09-06 12:59:32 +09:00
committed by GitHub
36 changed files with 735 additions and 1227 deletions
@@ -5,7 +5,6 @@ return {
show_playtime = true,
entity_protection = true,
player_reports = true,
player_warnings = true,
player_bans = true,
player_mutes = true,
player_kicks = true,
@@ -9,8 +9,7 @@ local ExpUtil = require("modules/exp_util")
local Gui = require("modules/exp_gui")
local Roles = require("modules/exp_roles")
local Reports = require("modules.exp_legacy.modules.control.reports") --- @dep modules.control.reports
local Warnings = require("modules.exp_legacy.modules.control.warnings") --- @dep modules.control.warnings
local Jail = require("modules.exp_legacy.modules.control.jail") --- @dep modules.control.jail
local Jail = require("modules/exp_scenario/control/jail")
local Colors = require("modules/exp_util/include/color")
local format_player_name = ExpUtil.format_player_name_locale
@@ -100,26 +99,12 @@ local function report_player_callback(player, reason)
Reports.report_player(selected_player.name, player.name, reason)
end
--- Gives the action player a warning, requires a reason
-- @element warn_player
local warn_player = new_button("utility/spawn_flag", { "exp-gui_player-list.warn-player" })
:on_click(function(def, player, element)
set_selected_action(player, "exp_scenario.command.create_warning")
end)
local function warn_player_callback(player, reason)
local selected_player, selected_player_color = get_action_player(player)
local by_player_name_color = format_player_name(player)
game.print{ "exp-commands_warnings.create", selected_player_color, by_player_name_color, reason }
Warnings.add_warning(selected_player.name, player.name, reason)
end
--- Jails the action player, requires a reason
-- @element jail_player
local jail_player = new_button("utility/multiplayer_waiting_icon", { "exp-gui_player-list.jail-player" })
:on_click(function(def, player, element)
local selected_player, selected_player_color = get_action_player(player)
if Jail.is_jailed(selected_player.name) then
if Jail.is_jailed(selected_player) then
player.print({ "exp-commands_jail.already-jailed", selected_player_color }, Colors.orange_red)
else
set_selected_action(player, "exp_scenario.command.jail")
@@ -130,7 +115,7 @@ local function jail_player_callback(player, reason)
local selected_player, selected_player_color = get_action_player(player)
local by_player_name_color = format_player_name(player)
game.print{ "exp-commands_jail.jailed", selected_player_color, by_player_name_color, reason }
Jail.jail_player(selected_player.name, player.name, reason)
Jail.jail_player(selected_player, player.name, reason)
end
--- Kicks the action player, requires a reason
@@ -170,18 +155,11 @@ return {
["exp_scenario.command.create_report"] = {
auth = function(player, selected_player)
if player == selected_player then return false end
if not Roles.player_has_permission(player, "exp_scenario.command.create_warning") then
return not Roles.player_has_permission(selected_player, "exp_scenario.bypass.reports")
end
end, -- can report any player that isn't immune and you aren't able to give warnings
return not Roles.player_has_permission(selected_player, "exp_scenario.bypass.reports")
end, -- can report any player that isn't immune
reason_callback = report_player_callback,
report_player,
},
["exp_scenario.command.create_warning"] = {
auth = Roles.player_outranks, -- warn a lower user, replaces report
reason_callback = warn_player_callback,
warn_player,
},
["exp_scenario.command.jail"] = {
auth = Roles.player_outranks,
reason_callback = jail_player_callback,
-38
View File
@@ -1,38 +0,0 @@
--- Config file for the warning system, this is very similar to reports but is for the use of moderators rather than normal users.
-- @config Warnings
return {
--- @type (LocalisedString[] | fun(player: LuaPlayer, by_player_name: string, number_of_warnings: number))[]
actions = { --- @setting actions what actions are taking at number of warnings
-- if a localized string is used then __1__ will by_player_name and __2__ will be the current warning count (auto inserted)
{ "warnings.received", "" },
{ "warnings.received", "" },
{ "warnings.received", { "warnings.pre-kick" } },
function(player, by_player_name, number_of_warnings)
local str = {
"You received a warning from ",
by_player_name,
". You have ",
number_of_warnings,
" warnings. You were kicked for having too many warnings; you may rejoin if you wish.",
}
game.kick_player(player, table.concat(str, "")) -- Does not support locale strings
-- game.kick_player(player, { "warnings.received", by_player_name, number_of_warnings, { "warnings.kick" } })
end,
{ "warnings.received", { "warnings.pre-pre-ban" } },
{ "warnings.received", { "warnings.pre-ban" } },
function(player, by_player_name, number_of_warnings)
local str = {
"You received a warning from ",
by_player_name,
". You have ",
number_of_warnings,
" warnings. You were banned for having too many warnings; visit https://www.explosivegaming.nl to request a ban appeal.",
}
game.kick_player(player, table.concat(str, "")) -- Does not support locale strings
-- game.ban_player(player, { "warnings.received", by_player_name, number_of_warnings, { "warnings.ban", { "links.website" } } })
end,
},
script_warning_cool_down = 30, --- @setting script_warning_cool_down time for a script warning (given by script) to be removed (in minutes)
script_warning_limit = 5, --- @setting script_warning_limit the number of script warnings (given by script) that are allowed before full warnings are given
}
-11
View File
@@ -18,14 +18,3 @@ read-readme=Make sure you have read the information gui (It can be found through
softmod=We run a softmod on our servers. A softmod is a custom scenario that runs on this server, an example is the player list.
redmew=We don't talk about redmew here; they beat us to 1000 members ;-;
lhd=All trains must be LHD! This is a long standing rule on our servers, please respect this.
[warnings]
received=You received a warning from __1__. You have __2__ warnings. __3__
pre-kick=This is your last warning before you are kicked.
kick=You were kicked for having too many warnings; you may rejoin if you wish.
pre-pre-ban=You are close to receiving a ban; successful ban appeals are unlikely.
pre-ban=This your LAST warning before you are BANNED! Successful ban appeals are unlikely.
ban=You were banned for having too many warnings; visit __1__ to request a ban appeal.
script-warning=You are receiving script warnings; if you recive too many you will receive a permanent warning (__1__/__2__)
script-warning-removed=A script warning has expired (__1__/__2__)
script-warning-limit=__1__ has received a permanent warning from the script.
-5
View File
@@ -10,11 +10,6 @@ message-cleared=Your join message has been cleared.
[quickbar]
saved=Your quickbar filters have been saved.
[exp-required]
Warnings=Warnings
Warnings-tooltip=The total number of warnings you have received from staff
Warnings-value-tooltip=The total number of warnings you have received from staff
[exp-settings]
Colour=Colour
Colour-tooltip=Your player colour
-1
View File
@@ -7,7 +7,6 @@ reason-entry=Enter Reason
goto-player=Goto player
bring-player=Bring player
report-player=Report player
warn-player=Warn player
jail-player=Jail player
kick-player=Kick player
ban-player=Ban player
-11
View File
@@ -18,14 +18,3 @@ read-readme=確保你已閱讀相關資訊。按左上 i 圖標可再次查看
softmod=這裹用了自設情境,是一種軟裝模組。
redmew=
lhd=列車必須是左則通行。這是本服務器長久以來的規則。
[warnings]
received=你已被 __1__ 警告了,現在共有 __2__ 個警告。 __3__
pre-kick=這是在被踢離之前的最後警告。
kick=你已因為被警告太多次而被請離,不過你之後還是可以回來的。
pre-pre-ban=按照你的行為,你有可能會被封禁。
pre-ban=這是在被封禁之前的最後警告。
ban=你已因為被警告太多次而被封禁; 可以到 __1__ 申訴.
script-warning=這是系統發出的自動警告 (__1__/__2__)
script-warning-removed=系統發出的自動警告已失效 (__1__/__2__)
script-warning-limit=__1__ 已被系統發出了一則自動警告。
-5
View File
@@ -10,11 +10,6 @@ message-cleared=你的加入信息已清除。
[quickbar]
saved=你的工具列已儲存。
[exp-required]
Warnings=警告
Warnings-tooltip=你所有收到的警告
Warnings-value-tooltip=你所有收到的警告
[exp-settings]
Colour=顏色
Colour-tooltip=你的人物顏色
-1
View File
@@ -7,7 +7,6 @@ reason-entry=輸入原因
goto-player=傳送到用戶
bring-player=傳送用戶到自己
report-player=舉報用戶
warn-player=警告用戶
jail-player=監禁用戶
kick-player=踢除用戶
ban-player=封禁用戶
-11
View File
@@ -18,14 +18,3 @@ read-readme=確保你已閱讀相關資訊。按左上 i 圖標可再次查看
softmod=這裹用了自設情境,是一種軟裝模組。
redmew=
lhd=列車必須是左則通行。這是本服務器長久以來的規則。
[warnings]
received=你已被 __1__ 警告了,現在共有 __2__ 個警告。 __3__
pre-kick=這是在被踢離之前的最後警告。
kick=你已因為被警告太多次而被請離,不過你之後還是可以回來的。
pre-pre-ban=按照你的行為,你有可能會被封禁。
pre-ban=這是在被封禁之前的最後警告。
ban=你已因為被警告太多次而被封禁; 可以到 __1__ 申訴.
script-warning=這是系統發出的自動警告 (__1__/__2__)
script-warning-removed=系統發出的自動警告已失效 (__1__/__2__)
script-warning-limit=__1__ 已被系統發出了一則自動警告。
-5
View File
@@ -10,11 +10,6 @@ message-cleared=你的加入信息已清除。
[quickbar]
saved=你的工具列已儲存。
[exp-required]
Warnings=警告
Warnings-tooltip=你所有收到的警告
Warnings-value-tooltip=你所有收到的警告
[exp-settings]
Colour=顏色
Colour-tooltip=你的人物顏色
-1
View File
@@ -7,7 +7,6 @@ reason-entry=輸入原因
goto-player=傳送到用戶
bring-player=傳送用戶到自己
report-player=舉報用戶
warn-player=警告用戶
jail-player=監禁用戶
kick-player=踢除用戶
ban-player=封禁用戶
-119
View File
@@ -1,119 +0,0 @@
--[[-- Control Module - Jail
- Adds a way to jail players.
@control Jail
@alias Jail
@usage
-- import the module from the control modules
local Jail = require("modules.exp_legacy.modules.control.jail") --- @dep modules.control.jail
-- This will give 'MrBiter' the jail role, which suppresses all of their other roles
-- the player name and reason are only so they can be included in the event for user feedback
Jail.jail_player('MrBiter', 'Cooldude2606', 'Likes biters too much')
-- This will remove the jail role from 'MrBiter', restoring their other roles
-- again as above the player name is only used in the event for user feedback
Jail.unjail_player('MrBiter', 'Cooldude2606')
]]
local Roles = require("modules/exp_roles")
local valid_player = function(p) return type(p) == "userdata" and p or game.get_player(p) end
--- The role given to jailed players, it has a higher priority than every other role
local function jail_role()
return assert(Roles.get_role_by_name("Jail"), "The Jail role does not exist")
end
local Jail = {
events = {
--- When a player is assigned to jail
-- @event on_player_jailed
-- @tparam number player_index the index of the player who was jailed
-- @tparam string by_player_name the name of the player who jailed the other player
-- @tparam string reason the reason that the player was jailed
on_player_jailed = script.generate_event_name(),
--- When a player is unassigned from jail
-- @event on_player_unjailed
-- @tparam number player_index the index of the player who was unjailed
-- @tparam string by_player_name the name of the player who unjailed the other player
on_player_unjailed = script.generate_event_name(),
},
}
--- Used to emit the jail related events
-- @tparam number event the name of the event that will be emited
-- @tparam LuaPlayer player the player who is being acted on
-- @tparam string by_player_name the player who is doing the action
-- @tparam string reason the reason for the action (jail)
local function event_emit(event, player, by_player_name, reason)
script.raise_event(event, {
name = event,
tick = game.tick,
player_index = player.index,
by_player_name = by_player_name,
reason = reason,
})
end
--- Jail.
-- Functions related to jail
-- @section jail-functions
--- Checks if the player is currently in jail
-- @tparam LuaPlayer player the player to check if they are in jail
-- @treturn boolean whether the player is currently in jail
function Jail.is_jailed(player)
local valid = valid_player(player)
return valid ~= nil and jail_role():has_player(valid)
end
--- Moves a player to jail, which suppresses all of their other roles
-- @tparam LuaPlayer player the player who will be jailed
-- @tparam string by_player_name the name of the player who is doing the jailing
-- @tparam[opt='Non given.'] string reason the reason that the player is being jailed
-- @treturn boolean wheather the user was jailed successfully
function Jail.jail_player(player, by_player_name, reason)
player = valid_player(player)
if not player then return end
if not by_player_name then return end
reason = reason or "Non given."
local role = jail_role()
if role:has_player(player) then return end
player.walking_state = { walking = false, direction = player.walking_state.direction }
player.riding_state = { acceleration = defines.riding.acceleration.nothing, direction = player.riding_state.direction }
player.mining_state = { mining = false }
player.shooting_state = { state = defines.shooting.not_shooting, position = player.shooting_state.position }
player.picking_state = false
player.repair_state = { repairing = false, position = player.repair_state.position }
role:assign(player, { by_player_name = by_player_name, silent = true })
event_emit(Jail.events.on_player_jailed, player, by_player_name, reason)
return true
end
--- Moves a player out of jail, which restores all of their other roles
-- @tparam LuaPlayer player the player that will be unjailed
-- @tparam string by_player_name the name of the player that is doing the unjail
-- @treturn boolean whether the player was unjailed successfully
function Jail.unjail_player(player, by_player_name)
player = valid_player(player)
if not player then return end
if not by_player_name then return end
local role = jail_role()
if not role:has_player(player) then return end
role:unassign(player, { by_player_name = by_player_name, silent = true })
event_emit(Jail.events.on_player_unjailed, player, by_player_name)
return true
end
return Jail
@@ -1,204 +0,0 @@
--[[-- Control Module - Protection
- Controls protected entities
@control Protection
@alias Protection
]]
local Storage = require("modules/exp_util/storage")
local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event
local config = require("modules.exp_legacy.config.protection") --- @dep config.protection
local EntityProtection = {
protected_entity_names = table.deep_copy(config.always_protected_names),
protected_entity_types = table.deep_copy(config.always_protected_types),
events = {
--- When a player mines a protected entity
-- @event on_player_mined_protected
-- @tparam number player_index the player index of the player who got mined the entity
-- @tparam LuaEntity entity the entity which was mined
on_player_mined_protected = script.generate_event_name(),
--- When a player repeatedly mines protected entities
-- @event on_repeat_violation
-- @tparam number player_index the player index of the player who got mined the entities
-- @tparam LuaEntity entity the last entity which was mined
on_repeat_violation = script.generate_event_name(),
},
}
-- Convert config tables into lookup tables
for _, config_key in ipairs{ "always_protected_names", "always_protected_types", "always_trigger_repeat_names", "always_trigger_repeat_types" } do
local tbl = config[config_key]
for key, value in ipairs(tbl) do
tbl[key] = nil
tbl[value] = true
end
end
local Roles = require("modules/exp_roles")
----- Storage Variables -----
--- Variables stored in the global table
local protected_entities = {} -- All entities which are protected
local protected_areas = {} -- All areas which are protected
local repeats = {} -- Stores repeat removals by players
Storage.register({
protected_entities = protected_entities,
protected_areas = protected_areas,
repeats = repeats,
}, function(tbl)
protected_entities = tbl.protected_entities
protected_areas = tbl.protected_areas
repeats = tbl.repeats
end)
----- Local Functions -----
--- Functions used internally to search and add to the protected array
--- Get the key used in protected_entities
local function get_entity_key(entity)
return string.format("%i,%i", math.floor(entity.position.x), math.floor(entity.position.y))
end
--- Get the key used in protected_areas
local function get_area_key(area)
return string.format("%i,%i", math.floor(area.left_top.x), math.floor(area.left_top.y))
end
--- Check if an entity is always protected
local function check_always_protected(entity)
return config.always_protected_names[entity.name] or config.always_protected_types[entity.type] or false
end
--- Check if an entity always triggers repeat protection
local function check_always_trigger_repeat(entity)
return config.always_trigger_repeat_names[entity.name] or config.always_trigger_repeat_types[entity.type] or false
end
----- Public Functions -----
--- Functions used to add and remove protected entities
--- Add an entity to the protected list
function EntityProtection.add_entity(entity)
local entities = protected_entities[entity.surface.index]
if not entities then
entities = {}
protected_entities[entity.surface.index] = entities
end
entities[get_entity_key(entity)] = entity
end
--- Remove an entity from the protected list
function EntityProtection.remove_entity(entity)
local entities = protected_entities[entity.surface.index]
if not entities then return end
entities[get_entity_key(entity)] = nil
end
--- Get all protected entities on a surface
function EntityProtection.get_entities(surface)
return protected_entities[surface.index] or {}
end
--- Check if an entity is protected
function EntityProtection.is_entity_protected(entity)
if check_always_protected(entity) then return true end
local entities = protected_entities[entity.surface.index]
if not entities then return false end
return entities[get_entity_key(entity)] == entity
end
--- Add an area to the protected list
function EntityProtection.add_area(surface, area)
local areas = protected_areas[surface.index]
if not areas then
areas = {}
protected_areas[surface.index] = areas
end
areas[get_area_key(area)] = area
end
--- Remove an area from the protected list
function EntityProtection.remove_area(surface, area)
local areas = protected_areas[surface.index]
if not areas then return end
areas[get_area_key(area)] = nil
end
--- Get all protected areas on a surface
function EntityProtection.get_areas(surface)
return protected_areas[surface.index] or {}
end
--- Check if an entity is protected
function EntityProtection.is_position_protected(surface, position)
local areas = protected_areas[surface.index]
if not areas then return false end
for _, area in pairs(areas) do
if area.left_top.x <= position.x and area.left_top.y <= position.y
and area.right_bottom.x >= position.x and area.right_bottom.y >= position.y
then
return true
end
end
return false
end
----- Events -----
--- All events registered by this module
--- Raise events for protected entities
Event.add(defines.events.on_pre_player_mined_item, function(event)
local entity = event.entity
local player = game.players[event.player_index]
-- Check if the player should be ignored
if config.ignore_admins and player.admin then return end
if entity.last_user == nil or entity.last_user.index == player.index then return end
if config.ignore_permission and Roles.player_has_permission(player, config.ignore_permission) then return end
-- Check if the entity is protected
if EntityProtection.is_entity_protected(entity)
or EntityProtection.is_position_protected(entity.surface, entity.position)
then
-- Update repeats
local player_repeats = repeats[player.name]
if not player_repeats then
player_repeats = { last = game.tick, count = 0 }
repeats[player.name] = player_repeats
end
player_repeats.last = game.tick
player_repeats.count = player_repeats.count + 1
-- Send events
event.name = EntityProtection.events.on_player_mined_protected
script.raise_event(EntityProtection.events.on_player_mined_protected, event)
if check_always_trigger_repeat(entity) or player_repeats.count >= config.repeat_count then
player_repeats.count = 0 -- Reset to avoid spamming of events
event.name = EntityProtection.events.on_repeat_violation
script.raise_event(EntityProtection.events.on_repeat_violation, event)
end
end
end)
--- Remove old repeats
Event.on_nth_tick(config.refresh_rate, function()
local old = game.tick - config.repeat_lifetime
for player_name, player_repeats in pairs(repeats) do
if player_repeats.last <= old then
repeats[player_name] = nil
end
end
end)
--- When an entity is removed remove it from the protection list
local function event_remove_entity(event)
EntityProtection.remove_entity(event.entity)
end
Event.add(defines.events.on_space_platform_pre_mined, event_remove_entity)
Event.add(defines.events.on_pre_player_mined_item, event_remove_entity)
Event.add(defines.events.on_robot_pre_mined, event_remove_entity)
Event.add(defines.events.on_entity_died, event_remove_entity)
Event.add(defines.events.script_raised_destroy, event_remove_entity)
return EntityProtection
@@ -1,173 +0,0 @@
local Storage = require("modules/exp_util/storage")
local Gui = require("modules/exp_gui")
local Event = require("modules/exp_legacy/utils/event") --- @dep utils.event
----- Locals -----
local follow_label --- @type ExpElement Gui constructor
local following = {}
local spectating = {}
local Public = {}
----- Storage data -----
Storage.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
local opened = player.opened
player.set_controller{ type = defines.controllers.spectator }
player.associate_character(character)
spectating[player.index] = character
if opened then player.opened = opened end -- Maintain opened after controller change
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] and Public.is_spectating(player) then
Public.stop_spectate(player)
end
Gui.destroy_if_valid(player.gui.screen.follow_label)
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
follow_label = Gui.define("follow-label")
:draw(function(def, parent, target)
Gui.destroy_if_valid(parent.follow_label)
local label = parent.add{
type = "label",
name = "follow_label",
style = "frame_title",
caption = "Following " .. target.name .. ".\nClick here or press esc to stop following.",
}
local player = Gui.get_player(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(function(def, player, element)
Public.stop_follow(player)
end)
:on_closed(function(def, player, element)
-- 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
@@ -1,331 +0,0 @@
--[[-- Control Module - Warnings
- Adds a way to give and remove warnings to players.
@control Warnings
@alias Warnings
@usage
-- import the module from the control modules
local Warnings = require("modules.exp_legacy.modules.control.warnings") --- @dep modules.control.warnings
-- This will add a warning to the player
Warnings.add_warning('MrBiter', 'Cooldude2606', 'Killed too many biters')
-- This will remove a warning from a player, second name is just who is doing the action
Warnings.remove_warning('MrBiter', 'Cooldude2606')
-- Script warning as similar to normal warning but are designed to have no effect for a short amount of time
-- this is so it can be used for greifer protection without being too agressive
Warnings.add_script_warning('MrBiter', 'Killed too many biters')
-- Both normal and script warnings can also be cleared, this will remove all warnings
Warnings.clear_warnings('MrBiter', 'Cooldude2606')
]]
local Event = require("modules/exp_legacy/utils/event")
local Storage = require("modules/exp_util/storage")
local config = require("modules.exp_legacy.config.warnings")
local valid_player = function(p) return type(p) == "userdata" and p or game.get_player(p) end
--- Stores the quickbar filters for a player
local PlayerData = require("modules.exp_legacy.expcore.player_data") --- @dep expcore.player_data
local PlayerWarnings = PlayerData.Required:combine("Warnings")
PlayerWarnings:set_metadata{
stringify = function(value)
if not value then return "You have no warnings" end
local count = 0
for _ in pairs(value) do count = count + 1 end
return "You have " .. count .. " warnings"
end,
}
local Warnings = {
user_warnings = PlayerWarnings,
user_script_warnings = {},
events = {
--- When a warning is added to a player
-- @event on_warning_added
-- @tparam number player_index the index of the player who recived the warning
-- @tparam string by_player_name the name of the player who gave the warning
-- @tparam string reason the reason that the player was given a warning
-- @tparam number warning_count the new number of warnings that the player has
on_warning_added = script.generate_event_name(),
--- When a warning is removed from a player
-- @event on_warning_removed
-- @tparam number player_index the index of the player who is having the warning removed
-- @tparam string warning_by_name the name of the player who gave the warning
-- @tparam string removed_by_name the name of the player who is removing the warning
-- @tparam number warning_count the new number of warnings that the player has
-- @tparam number batch_count the number of warnings removed in this batch, always one when not a batch
-- @tparam number batch the index of this event in a batch, always one when not a batch
on_warning_removed = script.generate_event_name(),
--- When a warning is added to a player, by the script
-- @event on_script_warning_added
-- @tparam number player_index the index of the player who recived the warning
-- @tparam string reason the reason that the player was given a warning
-- @tparam number warning_count the new number of warnings that the player has
on_script_warning_added = script.generate_event_name(),
--- When a warning is removed from a player, by the script
-- @event on_script_warning_removed
-- @tparam number player_index the index of the player who is having the warning removed
-- @tparam number warning_count the new number of warnings that the player has
on_script_warning_removed = script.generate_event_name(),
},
}
local user_script_warnings = Warnings.user_script_warnings
Storage.register(user_script_warnings, function(tbl)
Warnings.user_script_warnings = tbl
user_script_warnings = tbl
end)
--- Gets an array of warnings that the player has, always returns a list even if empty
-- @tparam LuaPlayer player the player to get the warning for
-- @treturn table an array of all the warnings on this player, contains tick, by_player_name and reason
function Warnings.get_warnings(player)
return PlayerWarnings:get(player.name, {})
end
--- Gets the number of warnings that a player has on them
-- @tparam LuaPlayer player the player to count the warnings for
-- @treturn number the number of warnings that the player has
function Warnings.count_warnings(player)
local warnings = PlayerWarnings:get(player.name, {})
return #warnings
end
--- Adds a warning to a player, when a warning is added a set action is done based on the number of warnings and the config file
-- @tparam LuaPlayer player the player to add a warning to
-- @tparam string by_player_name the name of the player who is doing the action
-- @tparam[opt='Non given.'] string reason the reason that the player is being warned
-- @treturn number the number of warnings that the player has
function Warnings.add_warning(player, by_player_name, reason)
player = valid_player(player)
if not player then return end
if not by_player_name then return end
reason = reason or "None given."
local warning_count --- @type number
PlayerWarnings:update(player.name, function(_, warnings)
local warning = {
by_player_name = by_player_name,
reason = reason,
}
if not warnings then
warning_count = 1
return { warning }
else
table.insert(warnings, warning)
warning_count = #warnings
end
end)
script.raise_event(Warnings.events.on_warning_added, {
name = Warnings.events.on_warning_added,
tick = game.tick,
player_index = player.index,
warning_count = warning_count,
by_player_name = by_player_name,
reason = reason,
})
local action = config.actions[warning_count]
if action then
local _type = type(action)
if _type == "function" then
action(player, by_player_name, warning_count)
elseif _type == "table" then
local current = table.deepcopy(action)
table.insert(current, 2, by_player_name)
table.insert(current, 3, warning_count)
player.print(current)
elseif type(action) == "string" then
player.print(action)
end
end
return warning_count
end
--- Event trigger for removing a waring due to it being looped in clear warnings
-- @tparam LuaPlayer player the player who is having a warning removed
-- @tparam string warning_by_name the name of the player who made the warning
-- @tparam string removed_by_name the name of the player who is doing the action
-- @tparam number warning_count the number of warnings that the player how has
-- @tparam number batch the index of this event in a batch, always one when not a batch
-- @tparam number batch_count the number of reports removed in this batch, always one when not a batch
local function warning_removed_event(player, warning_by_name, removed_by_name, warning_count, batch, batch_count)
script.raise_event(Warnings.events.on_warning_removed, {
name = Warnings.events.on_warning_removed,
tick = game.tick,
player_index = player.index,
warning_count = warning_count,
warning_by_name = warning_by_name,
removed_by_name = removed_by_name,
batch_count = batch_count or 1,
batch = batch or 1,
})
end
--- Removes a warning from a player, always removes the earliest warning, fifo
-- @tparam LuaPlayer player the player to remove a warning from
-- @tparam string by_player_name the name of the player who is doing the action
-- @treturn number the number of warnings that the player has
function Warnings.remove_warning(player, by_player_name)
player = valid_player(player)
if not player then return end
if not by_player_name then return end
local warning, warning_count
PlayerWarnings:update(player.name, function(_, warnings)
if not warnings then return end
warning = table.remove(warnings, 1)
warning_count = #warnings
end)
if not warning then return end
warning_removed_event(player, warning.by_player_name, by_player_name, warning_count)
return warning_count
end
--- Removes all warnings from a player, will trigger remove event for each warning
-- @tparam LuaPlayer player the player to clear the warnings from
-- @tparam string by_player_name the name of the player who is doing the action
-- @treturn boolean true when warnings were cleared succesfully
function Warnings.clear_warnings(player, by_player_name)
player = valid_player(player)
if not player then return end
if not by_player_name then return end
local warnings = PlayerWarnings:get(player)
if not warnings then return end
local warning_count = #warnings
for n, warning in pairs(warnings) do
warning_removed_event(player, warning.by_player_name, by_player_name, warning_count - n, n, warning_count)
end
PlayerWarnings:remove(player)
return true
end
--- Gets an array of all the script warnings that a player has
-- @tparam LuaPlayer player the player to get the script warnings of
-- @treturn table a table of all the script warnings a player has, contains tick and reason
function Warnings.get_script_warnings(player)
return user_script_warnings[player.name] or {}
end
--- Gets the number of script warnings that a player has on them
-- @tparam LuaPlayer player the player to count the script warnings of
-- @treturn number the number of script warnings that the player has
function Warnings.count_script_warnings(player)
local warnings = user_script_warnings[player.name] or {}
return #warnings
end
--- Adds a script warning to a player, this may add a full warning if max script warnings is met
-- @tparam LuaPlayer player the player to add a script warning to
-- @tparam[opt='Non given.'] string reason the reason that the player is being warned
-- @treturn number the number of script warnings that the player has
function Warnings.add_script_warning(player, reason)
player = valid_player(player)
if not player then return end
reason = reason or "Non given."
local warnings = user_script_warnings[player.name]
if not warnings then
warnings = {}
user_script_warnings[player.name] = warnings
end
table.insert(warnings, {
tick = game.tick,
reason = reason,
})
local warning_count = #warnings
script.raise_event(Warnings.events.on_script_warning_added, {
name = Warnings.events.on_script_warning_added,
tick = game.tick,
player_index = player.index,
warning_count = warning_count,
reason = reason,
})
if warning_count > config.script_warning_limit then
Warnings.add_warning(player, "<server>", reason)
end
return warning_count
end
--- Script warning removed event trigger due to it being looped in clear script warnings
-- @tparam LuaPlayer player the player who is having a script warning removed
-- @tparam number warning_count the number of warnings that the player has
local function script_warning_removed_event(player, warning_count)
script.raise_event(Warnings.events.on_script_warning_removed, {
name = Warnings.events.on_script_warning_removed,
tick = game.tick,
player_index = player.index,
warning_count = warning_count,
})
end
--- Removes a script warning from a player
-- @tparam LuaPlayer player the player to remove a script warning from
-- @treturn number the number of script warnings that the player has
function Warnings.remove_script_warning(player)
player = valid_player(player)
if not player then return end
local warnings = user_script_warnings[player.name]
if not warnings then return end
table.remove(warnings, 1)
script_warning_removed_event(player)
return #warnings
end
--- Removes all script warnings from a player, emits event for each warning removed
-- @tparam LuaPlayer player the player to clear the script warnings from
function Warnings.clear_script_warnings(player)
player = valid_player(player)
if not player then return end
local warnings = user_script_warnings[player.name]
if not warnings then return end
local warning_count = #warnings
for n, _ in pairs(warnings) do
script_warning_removed_event(player, warning_count - n)
end
user_script_warnings[player.name] = nil
return true
end
-- script warnings are removed after a certain amount of time to make them even more lienient
local script_warning_cool_down = config.script_warning_cool_down * 3600
Event.on_nth_tick(script_warning_cool_down / 4, function()
local cutoff = game.tick - script_warning_cool_down
for player_name, script_warnings in pairs(user_script_warnings) do
if #script_warnings > 0 then
for _, warning in pairs(script_warnings) do
if warning.tick < cutoff then
Warnings.remove_script_warning(player_name)
end
end
end
end
end)
return Warnings
+15 -23
View File
@@ -20,46 +20,38 @@ PlayerFilters:set_metadata{
end,
}
--- Filters are stored by a single index across the ten pages of ten slots
local total_page_count = 10
local slots_per_page = 10
--- Loads your quickbar preset
PlayerFilters:on_load(function(player_name, filters)
if not filters then filters = config[player_name] end
if not filters then return end
local player = game.players[player_name]
for i, item_name in pairs(filters) do
for index, item_name in pairs(filters) do
if item_name ~= nil and item_name ~= "" then
player.set_quick_bar_slot(i, item_name)
local page = math.ceil(index / slots_per_page)
local slot = (index - 1) % slots_per_page + 1
player.set_quick_bar_slot(page, slot, item_name)
end
end
end)
local ignored_items = {
["blueprint"] = true,
["blueprint-book"] = true,
["deconstruction-planner"] = true,
["spidertron-remote"] = true,
["upgrade-planner"] = true,
}
--- Saves your quickbar preset to the script-output folder
--- Saves your quickbar preset, only plain item filters can be saved
Commands.new("save-quickbar", "Saves your Quickbar preset items to file")
:add_aliases{ "save-toolbar" }
:add_flags{ "disabled" }
:register(function(player)
local filters = {}
error("2.1 changes to get_quick_bar_slot beak compatibility with 2.0; waiting for upstream")
-- Upstream may add method to compat, or change inventory sync to have a quickbar only mode
for i = 1, 100 do
--[[
local slot = player.get_quick_bar_slot(i)
-- Need to filter out blueprint and blueprint books because the slot is a LuaItemPrototype and does not contain a way to export blueprint data
if slot ~= nil then
local ignored = ignored_items[slot.name]
if ignored ~= true then
filters[i] = slot.name
for page = 1, total_page_count do
for slot = 1, slots_per_page do
-- Records, remotes and specific item instances hold data which can not be saved by name
local quick_bar_slot = player.get_quick_bar_slot(page, slot)
if quick_bar_slot and quick_bar_slot.type == "filter" then
filters[(page - 1) * slots_per_page + slot] = assert(quick_bar_slot.filter).name
end
end
]]
end
if next(filters) then
+39 -12
View File
@@ -113,16 +113,47 @@ if config.MachinesRemoved or config.TreesDestroyed or config.OreMined then
Event.add(defines.events.on_player_mined_entity, on_event)
end
--- Vehicles which credit their driver, or failing that their passenger, with what they hit
local crewed_vehicles = { car = true, ["spider-vehicle"] = true, locomotive = true }
--- Get the connected player behind the cause of damage or a death, nil for turrets, trains without a crew and the like
--- @param cause LuaEntity?
--- @return LuaPlayer?
local function get_cause_player(cause)
if not cause or not cause.valid then return nil end
local occupant = cause --- @type LuaEntity | LuaPlayer
if crewed_vehicles[cause.type] then
local crew = cause.get_driver() or cause.get_passenger()
if not crew then return nil end
occupant = crew
end
local player --- @type LuaPlayer?
if occupant.object_name == "LuaPlayer" then
player = occupant --[[@as LuaPlayer]]
elseif occupant.type == "character" then
player = occupant.player
end
if not player or not player.valid or not player.connected then return nil end
return player
end
--- Check that damage or a death counts against an entity, it must be hostile to the player
--- @param entity LuaEntity
--- @param player LuaPlayer
--- @return boolean
local function is_hostile(entity, player)
return entity.valid and entity.force ~= player.force and entity.force.name ~= "neutral"
end
--- Add DamageDealt if it is enabled
if config.DamageDealt then
local stat = Statistics:combine("DamageDealt")
Event.add(defines.events.on_entity_damaged, function(event)
local character = event.cause -- Check character is valid
if not character or not character.valid or character.type ~= "character" then return end
local player = character.player -- Check player is valid
if not player.valid or not player.connected then return end
local entity = event.entity -- Check entity is valid
if not entity.valid or entity.force == player.force or entity.force.name == "neutral" then return end
local player = get_cause_player(event.cause)
if not player or not is_hostile(event.entity, player) then return end
stat:increment(player, floor(event.final_damage_amount))
end)
end
@@ -131,12 +162,8 @@ end
if config.Kills then
local stat = Statistics:combine("Kills")
Event.add(defines.events.on_entity_died, function(event)
local character = event.cause -- Check character is valid
if not character or not character.valid or character.type ~= "character" then return end
local player = character.player -- Check player is valid
if not player or not player.valid or not player.connected then return end
local entity = event.entity -- Check entity is valid
if not entity.valid or entity.force == player.force or entity.force.name == "neutral" then return end
local player = get_cause_player(event.cause)
if not player or not is_hostile(event.entity, player) then return end
stat:increment(player)
end)
end