Move files to exp_legacy

This commit is contained in:
Cooldude2606
2024-09-23 15:55:28 +01:00
parent 446e87b610
commit 65145b5d34
266 changed files with 73 additions and 0 deletions

View File

@@ -0,0 +1,54 @@
--- Adds a better method of player starting items based on production levels.
-- @addon Advanced-Start
local Event = require 'utils.event' --- @dep utils.event
local config = require 'config.advanced_start' --- @dep config.advanced_start
local items = config.items
Event.add(defines.events.on_player_created, function(event)
local player = game.players[event.player_index]
-- game init settings
if event.player_index == 1 then
player.force.friendly_fire = config.friendly_fire
game.map_settings.enemy_expansion.enabled = config.enemy_expansion
local r = config.chart_radius
local p = player.position
player.force.chart(player.surface, {{p.x-r, p.y-r}, {p.x+r, p.y+r}})
end
-- spawn items
for item, callback in pairs(items) do
if type(callback) == 'function' then
local stats = player.force.item_production_statistics
local made = stats.get_input_count(item)
local success, count = pcall(callback, made, stats.get_input_count, player)
count = math.floor(count)
if success and count > 0 then
player.insert{name=item, count=count}
end
end
end
if config.armor.enable then
player.insert{name=config.armor.main, count=1}
for _, item in pairs(config.armor.item) do
player.insert{name=item.equipment, count=item.count}
end
end
end)
Event.on_init(function()
remote.call('freeplay', 'set_created_items', {})
remote.call('freeplay', 'set_chart_distance', 0)
remote.call('freeplay', 'set_skip_intro', config.skip_intro)
if config.research_queue_from_start then
for _, force in pairs(game.forces) do
force.research_queue_enabled = true
end
end
if not config.disable_base_game_silo_script then
if config.skip_victory then
remote.call('silo_script', 'set_no_victory', true)
end
end
end)

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 player.admin
or config.trust_as_active and player.online_time > config.trust_time
or config.active_role and (Roles.get_player_highest_role(player).index >= Roles.get_role_from_any(config.active_role).index) 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

@@ -0,0 +1,38 @@
--- Creates flying text entities when a player sends a message in chat;
-- also displays a ping above users who are named in the message
-- @addon Chat-Popups
local Game = require 'utils.game' --- @dep utils.game
local Event = require 'utils.event' --- @dep utils.event
local config = require 'config.popup_messages' --- @dep config.popup_messages
local send_text = Game.print_player_floating_text -- (player_index, text, color)
Event.add(defines.events.on_console_chat, function(event)
if not event.player_index or event.player_index < 1 then return end
local player = game.players[event.player_index]
-- Some basic sanity checks
if not player then return end
if not event.message then return end
-- Sends the message as text above them
if config.show_player_messages then
send_text(player.index, {'chat-popup.message', player.name, event.message}, player.chat_color)
end
if not config.show_player_mentions then return end
-- Makes lower and removes white space from the message
local search_string = event.message:lower():gsub("%s+", "")
-- Loops over online players to see if they name is included
for _, mentioned_player in pairs(game.connected_players) do
if mentioned_player.index ~= player.index then
if search_string:find(mentioned_player.name:lower(), 1, true) then
send_text(mentioned_player.index, {'chat-popup.ping', player.name}, player.chat_color)
end
end
end
end)

View File

@@ -0,0 +1,52 @@
--- Adds auto replies to chat messages; as well as chat commands
-- @addon Chat-Reply
local Event = require 'utils.event' --- @dep utils.event
local Roles = require 'expcore.roles' --- @dep expcore.roles
local config = require 'config.chat_reply' --- @dep config.chat_reply
Event.add(defines.events.on_console_chat, function(event)
local player_index = event.player_index
if not player_index or player_index < 1 then return end
local player = game.players[player_index]
local message = event.message:lower():gsub("%s+", "")
local allowed = true
if config.command_admin_only and not player.admin then allowed = false end
if config.command_permission and not Roles.player_allowed(player, config.command_permission) then allowed = false end
local prefix = config.command_prefix
for key_word, reply in pairs(config.messages) do
if message:find(key_word) then
local is_command = message:find(prefix..key_word)
if type(reply) == 'function' then
reply = reply(player, is_command)
end
if is_command and allowed then
game.print{'chat-bot.reply', reply}
elseif is_command then
player.print{'chat-bot.disallow'}
elseif not allowed then
player.print{'chat-bot.reply', reply}
end
end
end
if not allowed then return end
for key_word, reply in pairs(config.commands) do
if message:find(prefix..key_word) then
if type(reply) == 'function' then
local msg = reply(player, true)
if reply then
game.print{'chat-bot.reply', msg}
end
else
game.print{'chat-bot.reply', reply}
end
end
end
end)

View File

@@ -0,0 +1,106 @@
--- Adds a compilatron that walks around the spawn area; adapted from redmew code
-- @addon Compilatron
local Event = require 'utils.event' --- @dep utils.event
local Global = require 'utils.global' --- @dep utils.global
local Task = require 'utils.task' --- @dep utils.task
local Token = require 'utils.token' --- @dep utils.token
local config = require 'config.compilatron' --- @dep config.compilatron
local messages = config.messages
local locations = config.locations
local Public = {
compilatrons={},
current_messages={}
}
Global.register({
compilatrons = Public.compilatrons,
current_messages = Public.current_messages
}, function(tbl)
Public.compilatrons = tbl.compilatrons
Public.current_messages = tbl.current_messages
end)
--- This will re-create the speech bubble after it de-spawns called with set_timeout
local callback =
Token.register(
function(data)
local ent = data.ent
local name = data.name
local msg_number = data.msg_number
local message =
ent.surface.create_entity(
{name = 'compi-speech-bubble', text = messages[name][msg_number], position = {0, 0}, source = ent}
)
Public.current_messages[name] = {message = message, msg_number = msg_number}
end
)
--- This will move the messages onto the next message in the loop
local function circle_messages()
for name, ent in pairs(Public.compilatrons) do
if not ent.valid then
Public.spawn_compilatron(game.players[1].surface, name)
end
local current_message = Public.current_messages[name]
local msg_number
local message
if current_message ~= nil then
message = current_message.message
if message ~= nil then
message.destroy()
end
msg_number = current_message.msg_number
msg_number = (msg_number < #messages[name]) and msg_number + 1 or 1
else
msg_number = 1
end
-- this calls the callback above to re-spawn the message after some time
Task.set_timeout_in_ticks(300, callback, {ent = ent, name = name, msg_number = msg_number})
end
end
Event.on_nth_tick(config.message_cycle, circle_messages)
--- This will add a compilatron to the global and start his message cycle
-- @tparam LuaEntity entity the compilatron entity that moves around
-- @tparam string name the name of the location that the compilatron is at
function Public.add_compilatron(entity, name)
if not entity and not entity.valid then
return
end
if name == nil then
return
end
Public.compilatrons[name] = entity
local message =
entity.surface.create_entity(
{name = 'compi-speech-bubble', text = messages[name][1], position = {0, 0}, source = entity}
)
Public.current_messages[name] = {message = message, msg_number = 1}
end
--- This spawns a new compilatron on a surface with the given location tag (not a position)
-- @tparam LuaSurface surface the surface to spawn the compilatron on
-- @tparam string location the location tag that is in the config file
function Public.spawn_compilatron(surface, location)
local position = locations[location]
local pos = surface.find_non_colliding_position('compilatron', position, 1.5, 0.5)
local compi = surface.create_entity {name='compilatron', position=pos, force=game.forces.neutral}
Public.add_compilatron(compi, location)
end
-- When the first player is created this will create all compilatrons that are resisted in the config
Event.add(defines.events.on_player_created, function(event)
if event.player_index ~= 1 then return end
local player = game.players[event.player_index]
for location in pairs(locations) do
Public.spawn_compilatron(player.surface, location)
end
end)
return Public

View File

@@ -0,0 +1,42 @@
--- Displays the amount of dmg that is done by players to entities;
-- also shows player health when a player is attacked
-- @addon Damage-Popups
local Game = require 'utils.game' --- @dep utils.game
local Event = require 'utils.event' --- @dep utils.event
local config = require 'config.popup_messages' --- @dep config.popup_messages
Event.add(defines.events.on_entity_damaged, function(event)
local entity = event.entity
local cause = event.cause
local damage = math.floor(event.original_damage_amount)
local health = math.floor(entity.health)
local health_percentage = entity.get_health_ratio()
local text_colour = {r=1-health_percentage, g=health_percentage, b=0}
-- Gets the location of the text
local size = entity.get_radius()
if size < 1 then size = 1 end
local r = (math.random()-0.5)*size*config.damage_location_variance
local p = entity.position
local position = {x=p.x+r, y=p.y-size}
-- Sets the message
local message
if entity.name == 'character' and config.show_player_health then
message = {'damage-popup.player-health', health}
elseif entity.name ~= 'character' and cause and cause.name == 'character' and config.show_player_damage then
message = {'damage-popup.player-damage', damage}
end
-- Outputs the message as floating text
if message then
Game.print_floating_text(
entity.surface,
position,
message,
text_colour
)
end
end)

View File

@@ -0,0 +1,149 @@
--- Makes markers on the map where places have died and reclaims items if not recovered
-- @addon Death-Logger
local Event = require 'utils.event' --- @dep utils.event
local Global = require 'utils.global' --- @dep utils.global
local config = require 'config.death_logger' --- @dep config.death_logger
local format_time, move_items = _C.format_time, _C.move_items_stack --- @dep expcore.common
-- Max amount of ticks a corpse can be alive
local corpse_lifetime = 60*60*15
local deaths = {
archive={} -- deaths moved here after body is gone
--{player_name='Cooldude2606', time_of_death='15H 15M', position={x=0, y=0}, corpse=LuaEntity, tag=LuaCustomChartTag}
}
Global.register(deaths, function(tbl)
deaths = tbl
end)
--- Creates a new death marker and saves it to the given death
local function create_map_tag(death)
local player = game.players[death.player_name]
local message = player.name..' died'
if config.include_time_of_death then
local time = format_time(death.time_of_death, {hours=true, minutes=true, string=true})
message = message..' at '..time
end
death.tag = player.force.add_chart_tag(player.surface, {
position=death.position,
icon=config.map_icon,
text=message
})
end
--- Checks that all map tags are present and valid
-- adds missing ones, deletes expired ones
local function check_map_tags()
for index, death in ipairs(deaths) do
local map_tag = death.tag
local corpse = death.corpse
-- Check the corpse is valid
if corpse and corpse.valid then
-- Corpse is valid check the map tag
if not map_tag or not map_tag.valid then
-- Map tag is not valid make a new one
create_map_tag(death)
end
else
-- Corpse is not valid so remove the map tag
if map_tag and map_tag.valid then
map_tag.destroy()
end
-- Move the death to the archive
death.corpse = nil
death.tag = nil
table.insert(deaths.archive, death)
table.remove(deaths, index)
end
end
end
-- when a player dies a new death is added to the records and a map marker is made
Event.add(defines.events.on_player_died, function(event)
local player = game.players[event.player_index]
local corpse = player.surface.find_entity('character-corpse', player.position)
if config.use_chests_as_bodies then
local items = corpse.get_inventory(defines.inventory.character_corpse)
local chest = move_items(items, corpse.surface, corpse.position)
chest.destructible = false
corpse.destroy()
corpse = chest
end
local death = {
player_name = player.name,
time_of_death = event.tick,
position = player.position,
corpse = corpse
}
if config.show_map_markers then
create_map_tag(death)
end
table.insert(deaths, death)
-- Draw a light attached to the corpse with the player color
if config.show_light_at_corpse then
rendering.draw_light{
sprite = 'utility/light_medium',
color = player.color,
target = corpse,
force = player.force,
surface = player.surface
}
end
end)
-- Draw lines to the player corpse
if config.show_line_to_corpse then
Event.add(defines.events.on_player_respawned, function(event)
local player = game.players[event.player_index]
-- New deaths are added at the end of the deaths array, this is why
-- we are itterating over the array in reverse. This saves on the amount
-- of itterations we do.
for index = #deaths, 1, -1 do
local death = deaths[index]
-- If the corpse has already expired break out of the loop because
-- all the deaths that will follow will be expired.
if game.tick - death.time_of_death > corpse_lifetime then break end
-- Check if the death body is from the player
-- Check if the corpse entity is still valid
if death.player_name == player.name and death.corpse and death.corpse.valid then
local line_color = player.color
line_color.a = .3
rendering.draw_line{
color = line_color,
from = player.character,
to = death.corpse,
players = { event.player_index },
width = 2,
dash_length = 1,
gap_length = 1,
surface = player.surface,
draw_on_ground = true
}
end
end
end)
end
-- every 5 min all bodies are checked for valid map tags
if config.show_map_markers then
local check_period = 60*60*5 -- five minutes
Event.on_nth_tick(check_period, function()
check_map_tags()
end)
end
if config.auto_collect_bodies then
Event.add(defines.events.on_character_corpse_expired, function(event)
local corpse = event.corpse
local items = corpse.get_inventory(defines.inventory.character_corpse)
move_items(items, corpse.surface, {x=0, y=0})
end)
end
-- this is so other modules can access the logs
return deaths

View File

@@ -0,0 +1,141 @@
--- Log certain actions into a file when events are triggered
-- @addon Deconlog
local Event = require 'utils.event' --- @dep utils.event
local Roles = require 'expcore.roles' --- @dep expcore.roles
local format_time = _C.format_time --- @dep expcore.common
local format_number = require('util').format_number --- @dep util
local config = require 'config.deconlog' --- @dep config.deconlog
local filepath = "log/decon.log"
local function add_log(data)
game.write_file(filepath, data .. "\n", true, 0) -- write data
end
local function get_secs()
return format_time(game.tick, { hours = true, minutes = true, seconds = true, string = true })
end
local function pos_to_string(pos)
return tostring(pos.x) .. "," .. tostring(pos.y)
end
local function pos_to_gps_string(pos)
return '[gps=' .. string.format('%.1f', pos.x) .. ',' .. string.format('%.1f', pos.y) .. ']'
end
--- 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
Event.on_init(function()
game.write_file(filepath, "\n", false, 0) -- write data
end)
if config.decon_area then
Event.add(defines.events.on_player_deconstructed_area, function(e)
if e.alt then
return
end
local player = game.get_player(e.player_index)
if Roles.player_has_flag(player, 'deconlog-bypass') then
return
end
local items = e.surface.find_entities_filtered{area=e.area, force=player.force}
if #items > 250 then
print_to_players(true, {'deconlog.decon', player.name, e.surface.name, pos_to_gps_string(e.area.left_top), pos_to_gps_string(e.area.right_bottom), format_number(#items)})
end
add_log(get_secs() .. ',' .. player.name .. ',decon_area,' .. e.surface.name .. ',' .. pos_to_string(e.area.left_top) .. ',' .. pos_to_string(e.area.right_bottom))
end)
end
if config.built_entity then
Event.add(defines.events.on_built_entity, function (e)
if not e.player_index then return end
local player = game.get_player(e.player_index)
if Roles.player_has_flag(player, "deconlog-bypass") then
return
end
local ent = e.created_entity
add_log(get_secs() .. "," .. player.name .. ",built_entity," .. ent.name .. "," .. pos_to_string(ent.position) .. "," .. tostring(ent.direction) .. "," .. tostring(ent.orientation))
end)
end
if config.mined_entity then
Event.add(defines.events.on_player_mined_entity, function (e)
local player = game.get_player(e.player_index)
if Roles.player_has_flag(player, "deconlog-bypass") then
return
end
local ent = e.entity
add_log(get_secs() .. "," .. player.name .. ",mined_entity," .. ent.name .. "," .. pos_to_string(ent.position) .. "," .. tostring(ent.direction) .. "," .. tostring(ent.orientation))
end)
end
if config.fired_rocket then
Event.add(defines.events.on_player_ammo_inventory_changed, function (e)
local player = game.get_player(e.player_index)
if Roles.player_has_flag(player, "deconlog-bypass") then
return
end
local ammo_inv = player.get_inventory(defines.inventory.character_ammo)
local item = ammo_inv[player.character.selected_gun_index]
if not item or not item.valid or not item.valid_for_read then
return
end
if item.name == "rocket" then
add_log(get_secs() .. "," .. player.name .. ",shot-rocket," .. pos_to_string(player.position) .. "," .. pos_to_string(player.shooting_state.position))
end
end)
end
if config.fired_explosive_rocket then
Event.add(defines.events.on_player_ammo_inventory_changed, function (e)
local player = game.get_player(e.player_index)
if Roles.player_has_flag(player, "deconlog-bypass") then
return
end
local ammo_inv = player.get_inventory(defines.inventory.character_ammo)
local item = ammo_inv[player.character.selected_gun_index]
if not item or not item.valid or not item.valid_for_read then
return
end
if item.name == "explosive-rocket" then
add_log(get_secs() .. "," .. player.name .. ",shot-explosive-rocket," .. pos_to_string(player.position) .. "," .. pos_to_string(player.shooting_state.position))
end
end)
end
if config.fired_nuke then
Event.add(defines.events.on_player_ammo_inventory_changed, function (e)
local player = game.get_player(e.player_index)
if Roles.player_has_flag(player, "deconlog-bypass") then
return
end
local ammo_inv = player.get_inventory(defines.inventory.character_ammo)
local item = ammo_inv[player.character.selected_gun_index]
if not item or not item.valid or not item.valid_for_read then
return
end
if item.name == "atomic-bomb" then
add_log(get_secs() .. "," .. player.name .. ",shot-nuke," .. pos_to_string(player.position) .. "," .. pos_to_string(player.shooting_state.position))
end
end)
end

View File

@@ -0,0 +1,303 @@
--- Sends alert messages to our discord server when certain events are triggered
-- @addon Discord-Alerts
local Event = require 'utils.event' --- @dep utils.event
local Colors = require 'utils.color_presets' --- @dep utils.color_presets
local write_json, format_time = _C.write_json, _C.format_time --- @dep expcore.common
local config = require 'config.discord_alerts' --- @dep config.discord_alerts
local playtime_format = {hours = true, minutes = true, short = true, string = true}
local function append_playtime(player_name)
if not config.show_playtime then
return player_name
end
local player = game.get_player(player_name)
if not player then
return player_name
end
return player.name ..' (' .. format_time(player.online_time, playtime_format) .. ')'
end
local function get_player_name(event)
local player = game.players[event.player_index]
return player.name, event.by_player_name
end
local function to_hex(color)
local hex_digits = '0123456789ABCDEF'
local function hex(bit)
local major, minor = math.modf(bit/16)
major, minor = major+1, minor*16+1
return hex_digits:sub(major, major) .. hex_digits:sub(minor, minor)
end
return '0x' .. hex(color.r) .. hex(color.g) .. hex(color.b)
end
local function emit_event(args)
local title = args.title or ''
local color = args.color or '0x0'
local description = args.description or ''
if type(color) == 'table' then
color = to_hex(color)
end
local tick = args.tick or game.tick
local tick_formatted = format_time(tick, {days = false, hours = true, minutes = true, short = true, string = true})
local players_online = 0
local admins_online = 0
for _, player in pairs(game.connected_players) do
players_online = players_online + 1
if player.admin then
admins_online = admins_online + 1
end
end
local done = {title=true, color=true, description=true}
local fields = {{
name='Server Details',
value=string.format('Server: ${serverName} Time: %s\nTotal: %d Online: %d Admins: %d', tick_formatted, #game.players, players_online, admins_online)
}}
for key, value in pairs(args) do
if not done[key] then
done[key] = true
local field = {
name=key,
value=value,
inline=false
}
local new_value, inline = value:gsub('<inline>', '', 1)
if inline > 0 then
field.value = new_value
field.inline = true
end
table.insert(fields, field)
end
end
write_json('ext/discord.out',{
title=title,
description=description,
color=color,
fields=fields
})
end
--- Repeated protected entity mining
if config.entity_protection then
local EntityProtection = require 'modules.control.protection' --- @dep modules.control.protection
Event.add(EntityProtection.events.on_repeat_violation, function(event)
local player_name = get_player_name(event)
emit_event{
title='Entity Protection',
description='A player removed protected entities',
color=Colors.yellow,
['Player']='<inline>' .. append_playtime(player_name),
['Entity']='<inline>' .. event.entity.name,
['Location']='X ' .. event.entity.position.x .. ' Y ' .. event.entity.position.y
}
end)
end
--- Reports added and removed
if config.player_reports then
local Reports = require 'modules.control.reports' --- @dep modules.control.reports
Event.add(Reports.events.on_player_reported, function(event)
local player_name, by_player_name = get_player_name(event)
emit_event{
title='Report',
description='A player was reported',
color=Colors.yellow,
['Player']='<inline>' .. append_playtime(player_name),
['By']='<inline>' .. append_playtime(by_player_name),
['Report Count']='<inline>' .. Reports.count_reports(player_name),
['Reason']=event.reason
}
end)
Event.add(Reports.events.on_report_removed, function(event)
if event.batch ~= 1 then return end
local player_name = get_player_name(event)
emit_event{
title='Reports Removed',
description='A player has a report removed',
color=Colors.green,
['Player']='<inline>' .. player_name,
['By']='<inline>' .. event.removed_by_name,
['Report Count']='<inline>' .. event.batch_count
}
end)
end
--- Warnings added and removed
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)
Event.add(Warnings.events.on_warning_removed, function(event)
if event.batch ~= 1 then return end
local player_name = get_player_name(event)
emit_event{
title='Warnings Removed',
description='A player has a warning removed',
color=Colors.green,
['Player']='<inline>' .. player_name,
['By']='<inline>' .. event.removed_by_name,
['Warning Count']='<inline>' .. event.batch_count
}
end)
end
--- When a player is jailed or unjailed
if config.player_jail then
local Jail = require 'modules.control.jail'
Event.add(Jail.events.on_player_jailed, function(event)
local player_name, by_player_name = get_player_name(event)
emit_event{
title='Jail',
description='A player has been jailed',
color=Colors.yellow,
['Player']='<inline>' .. player_name,
['By']='<inline>' .. by_player_name,
['Reason']=event.reason
}
end)
Event.add(Jail.events.on_player_unjailed, function(event)
local player_name, by_player_name = get_player_name(event)
emit_event{
title='Unjail',
description='A player has been unjailed',
color=Colors.green,
['Player']='<inline>' .. player_name,
['By']='<inline>' .. by_player_name
}
end)
end
--- Ban and unban
if config.player_bans then
Event.add(defines.events.on_player_banned, function(event)
if event.by_player then
local by_player = game.players[event.by_player]
emit_event{
title='Banned',
description='A player has been banned',
color=Colors.red,
['Player']='<inline>' .. event.player_name,
['By']='<inline>' .. by_player.name,
['Reason']=event.reason
}
end
end)
Event.add(defines.events.on_player_unbanned, function(event)
if event.by_player then
local by_player = game.players[event.by_player]
emit_event{
title='Un-Banned',
description='A player has been un-banned',
color=Colors.green,
['Player']='<inline>' .. event.player_name,
['By']='<inline>' .. by_player.name
}
end
end)
end
--- Mute and unmute
if config.player_mutes then
Event.add(defines.events.on_player_muted, function(event)
local player_name = get_player_name(event)
emit_event{
title='Muted',
description='A player has been muted',
color=Colors.yellow,
['Player']='<inline>' .. player_name
}
end)
Event.add(defines.events.on_player_unmuted, function(event)
local player_name = get_player_name(event)
emit_event{
title='Un-Muted',
description='A player has been un-muted',
color=Colors.green,
['Player']='<inline>' .. player_name
}
end)
end
--- Kick
if config.player_kicks then
Event.add(defines.events.on_player_kicked, function(event)
if event.by_player then
local player_name = get_player_name(event)
local by_player = game.players[event.by_player]
emit_event{
title='Kick',
description='A player has been kicked',
color=Colors.orange,
['Player']='<inline>' .. player_name,
['By']='<inline>' .. by_player.name,
['Reason']=event.reason
}
end
end)
end
--- Promote and demote
if config.player_promotes then
Event.add(defines.events.on_player_promoted, function(event)
local player_name = get_player_name(event)
emit_event{
title='Promote',
description='A player has been promoted',
color=Colors.green,
['Player']='<inline>' .. player_name
}
end)
Event.add(defines.events.on_player_demoted, function(event)
local player_name = get_player_name(event)
emit_event{
title='Demote',
description='A player has been demoted',
color=Colors.yellow,
['Player']='<inline>' .. player_name
}
end)
end
--- Other commands
Event.add(defines.events.on_console_command, function(event)
if event.player_index then
local player_name = get_player_name(event)
if config[event.command] then
emit_event{
title=event.command:gsub('^%l', string.upper),
description='/' .. event.command .. ' was used',
color=Colors.grey,
['By']='<inline>' .. player_name,
['Details'] = event.parameters ~= '' and event.parameters or nil
}
end
end
end)

View File

@@ -0,0 +1,19 @@
--- Allows the FAGC clientside bot to receive information about bans and unbans and propagate that information to other servers
-- @addon FAGC
local Event = require 'utils.event' --- @dep utils.event
-- Clear the file on startup to minimize its size
Event.on_init(function()
game.write_file("fagc-actions.txt", "", false, 0)
end)
Event.add(defines.events.on_player_banned, function(e)
local text = "ban;" .. e.player_name .. ";" .. (e.by_player or "") .. ";" .. (e.reason or "") .. "\n"
game.write_file("fagc-actions.txt", text, true, 0)
end)
Event.add(defines.events.on_player_unbanned, function(e)
local text = "unban;" .. e.player_name .. ";" .. (e.by_player or "") .. ";" .. (e.reason or "") .. "\n"
game.write_file("fagc-actions.txt", text, true, 0)
end)

View File

@@ -0,0 +1,23 @@
local Event = require 'utils.event'
local controllers_with_inventory = {
[defines.controllers.character] = true,
[defines.controllers.god] = true,
[defines.controllers.editor] = true,
}
Event.add(defines.events.on_player_mined_entity, function(event)
if (not event.entity.valid) or (event.entity.type ~= 'inserter') or event.entity.drop_target then
return
end
local item_entity = event.entity.surface.find_entity('item-on-ground', event.entity.drop_position)
if item_entity then
local player = game.get_player(event.player_index)
if controllers_with_inventory[player.controller_type] then
player.mine_entity(item_entity)
end
end
end)

View File

@@ -0,0 +1,15 @@
--- Will move players items to spawn when they are banned or kicked, option to clear on leave
-- @addon Inventory-Clear
local Event = require 'utils.event' --- @dep utils.event
local events = require 'config.inventory_clear' --- @dep config.inventory_clear
local move_items_stack = _C.move_items_stack --- @dep expcore.common
local function clear_items(event)
local player = game.players[event.player_index]
local inv = player.get_main_inventory()
move_items_stack(inv)
inv.clear()
end
for _, event_name in ipairs(events) do Event.add(event_name, clear_items) end

View File

@@ -0,0 +1,59 @@
--[[-- Addon Lawnmower
- Adds a command that clean up biter corpse and nuclear hole
@addon Lawnmower
]]
local Commands = require 'expcore.commands' --- @dep expcore.commands
local Event = require 'utils.event' --- @dep utils.event
local config = require 'config.lawnmower' --- @dep config.lawnmower
require 'config.expcore.command_general_parse'
Commands.new_command('lawnmower', {'expcom-lawnmower.description'}, 'Clean up biter corpse, decoratives and nuclear hole')
:add_param('range', false, 'integer-range', 1, 200)
:register(function(player, range)
local tile_to_do = {}
player.surface.destroy_decoratives({position=player.position, radius=range})
local entities = player.surface.find_entities_filtered{position=player.position, radius=range, type='corpse'}
for _, entity in pairs(entities) do
if (entity.name ~= 'transport-caution-corpse' and entity.name ~= 'invisible-transport-caution-corpse') then
entity.destroy()
end
end
local tiles = player.surface.find_tiles_filtered{position=player.position, radius=range, name={'nuclear-ground'}}
for _, tile in pairs(tiles) do
table.insert(tile_to_do, {name='grass-1', position=tile.position})
end
player.surface.set_tiles(tile_to_do)
return Commands.success
end)
local function destroy_decoratives(entity)
if entity.type ~= 'entity-ghost' and entity.type ~= 'tile-ghost' and entity.prototype.selectable_in_game then
entity.surface.destroy_decoratives{area=entity.selection_box}
end
end
if config.destroy_decoratives then
Event.add(defines.events.on_built_entity, function(event)
destroy_decoratives(event.created_entity)
end)
Event.add(defines.events.on_robot_built_entity, function(event)
destroy_decoratives(event.created_entity)
end)
Event.add(defines.events.script_raised_built, function(event)
destroy_decoratives(event.entity)
end)
Event.add(defines.events.script_raised_revive, function(event)
destroy_decoratives(event.entity)
end)
end

View File

@@ -0,0 +1,71 @@
--[[-- Addon Logging
@addon Logging
]]
local Event = require 'utils.event' --- @dep utils.event
local config = require 'config.logging' --- @dep config.logging
local config_res = require 'config.research' --- @dep config.research
local function add_log(data)
game.write_file(config.file_name, data, true, 0)
game.write_file(config.file_name, '\n', true, 0)
end
Event.add(defines.events.on_rocket_launched, function(event)
if event and event.rocket and event.rocket.force and event.rocket.force.rockets_launched then
if event.rocket.force.rockets_launched >= config.rocket_launch_display_rate and event.rocket.force.rockets_launched % config.rocket_launch_display_rate == 0 then
add_log('[ROCKET] ' .. event.rocket.force.rockets_launched .. ' rockets launched')
elseif config.rocket_launch_display[event.rocket.force.rockets_launched] then
add_log('[ROCKET] ' .. event.rocket.force.rockets_launched .. ' rockets launched')
end
end
end)
Event.add(defines.events.on_pre_player_died, function(event)
if event and event.player_index then
if event.cause then
if event.cause.type and event.cause.type == 'character' and event.cause.player and event.cause.player.index then
add_log('[DEATH] ' .. game.players[event.player_index].name .. ' died because of ' .. (game.players[event.cause.player.index].name or 'unknown reason'))
else
add_log('[DEATH] ' .. game.players[event.player_index].name .. ' died because of ' .. (event.cause.name or 'unknown reason'))
end
else
add_log('[DEATH] ' .. game.players[event.player_index].name .. ' died because of unknown reason')
end
end
end)
Event.add(defines.events.on_research_finished, function(event)
if event and event.research then
if event.by_script then
return
end
if (event.research.level and config_res.inf_res[event.research.name]) and (event.research.level >= config_res.inf_res[event.research.name]) then
add_log({'logging.add-l', event.research.prototype.localised_name, event.research.level - 1})
else
add_log({'logging.add-n', event.research.prototype.localised_name})
end
end
end)
Event.add(defines.events.on_player_joined_game, function(event)
if event and event.player_index then
add_log('[JOIN] ' .. game.players[event.player_index].name .. ' joined the game')
end
end)
Event.add(defines.events.on_player_left_game, function(event)
if event and event.player_index then
if event.reason then
add_log('[LEAVE] ' .. game.players[event.player_index].name .. config.disconnect_reason[event.reason])
else
add_log('[LEAVE] ' .. game.players[event.player_index].name .. config.disconnect_reason[defines.disconnect_reason.quit])
end
end
end)

View File

@@ -0,0 +1,181 @@
local Event = require 'utils.event_core' --- @dep utils.event_core
local Global = require 'utils.global' --- @dep utils.global
local config = require 'config.miner' --- @dep config.miner
local miner_data = {}
Global.register(miner_data, function(tbl)
miner_data = tbl
end)
miner_data.queue = {}
local function drop_target(entity)
if entity.drop_target then
return entity.drop_target
else
local entities = entity.surface.find_entities_filtered{position=entity.drop_position}
if #entities > 0 then
return entities[1]
end
end
end
local function check_entity(entity)
if entity.to_be_deconstructed(entity.force) then
-- if it is already waiting to be deconstruct
return true
end
if next(entity.circuit_connected_entities.red) ~= nil or next(entity.circuit_connected_entities.green) ~= nil then
-- connected to circuit network
return true
end
if not entity.minable then
-- if it is minable
return true
end
if not entity.prototype.selectable_in_game then
-- if it can select
return true
end
if entity.has_flag('not-deconstructable') then
-- if it can deconstruct
return true
end
return false
end
local function chest_check(entity)
local target = drop_target(entity)
if check_entity(entity) then
return
end
if target.type ~= 'logistic-container' and target.type ~= 'container' then
-- not a chest
return
end
local radius = 2
local entities = target.surface.find_entities_filtered{area={{target.position.x - radius, target.position.y - radius}, {target.position.x + radius, target.position.y + radius}}, type={'mining-drill', 'inserter'}}
for _, e in pairs(entities) do
if drop_target(e) == target then
if not e.to_be_deconstructed(entity.force) and e ~= entity then
return
end
end
end
if check_entity(target) then
table.insert(miner_data.queue, {t=game.tick + 10, e=target})
end
end
local function miner_check(entity)
local ep = entity.position
local es = entity.surface
local ef = entity.force
local er = entity.prototype.mining_drill_radius
for _, r in pairs(entity.surface.find_entities_filtered{area={{x=ep.x - er, y=ep.y - er}, {x=ep.x + er, y=ep.y + er}}, type='resource'}) do
if r.amount > 0 then
return
end
end
--[[
entity.status ~= defines.entity_status.no_minable_resources
]]
if check_entity(entity) then
return
end
local pipe_build = {}
if config.fluid and entity.fluidbox and #entity.fluidbox > 0 then
-- if require fluid to mine
table.insert(pipe_build, {x=0, y=0})
local half = math.floor(entity.get_radius())
local r = 1 + er
local entities = es.find_entities_filtered{area={{ep.x - r, ep.y - r}, {ep.x + r, ep.y + r}}, type={'mining-drill', 'pipe', 'pipe-to-ground'}}
local entities_t = es.find_entities_filtered{area={{ep.x - r, ep.y - r}, {ep.x + r, ep.y + r}}, ghost_type={'pipe', 'pipe-to-ground'}}
table.array_insert(entities, entities_t)
for _, e in pairs(entities) do
if (e.position.x > ep.x) and (e.position.y == ep.y) then
for h=1, half do
table.insert(pipe_build, {x=h, y=0})
end
elseif (e.position.x < ep.x) and (e.position.y == ep.y) then
for h=1, half do
table.insert(pipe_build, {x=-h, y=0})
end
elseif (e.position.x == ep.x) and (e.position.y > ep.y) then
for h=1, half do
table.insert(pipe_build, {x=0, y=h})
end
elseif (e.position.x == ep.x) and (e.position.y < ep.y) then
for h=1, half do
table.insert(pipe_build, {x=0, y=-h})
end
end
end
end
if config.chest then
chest_check(entity)
end
table.insert(miner_data.queue, {t=game.tick + 5, e=entity})
for _, pos in ipairs(pipe_build) do
es.create_entity{name='entity-ghost', position={x=ep.x + pos.x, y=ep.y + pos.y}, force=ef, inner_name='pipe', raise_built=true}
end
end
Event.add(defines.events.on_resource_depleted, function(event)
if event.entity.prototype.infinite_resource then
return
end
local entities = event.entity.surface.find_entities_filtered{area={{event.entity.position.x - 1, event.entity.position.y - 1}, {event.entity.position.x + 1, event.entity.position.y + 1}}, type='mining-drill'}
if #entities == 0 then
return
end
for _, entity in pairs(entities) do
if ((math.abs(entity.position.x - event.entity.position.x) < entity.prototype.mining_drill_radius) and (math.abs(entity.position.y - event.entity.position.y) < entity.prototype.mining_drill_radius)) then
miner_check(entity)
end
end
end)
Event.on_nth_tick(10, function(event)
for k, q in pairs(miner_data.queue) do
if not q.e or not q.e.valid then
table.remove(miner_data.queue, k)
break
elseif event.tick >= q.t then
q.e.order_deconstruction(q.e.force)
table.remove(miner_data.queue, k)
end
end
end)

View File

@@ -0,0 +1,46 @@
--- Disable new players from having certain items in their inventory, most commonly nukes
-- @addon Nukeprotect
local Event = require 'utils.event' --- @dep utils.event
local Roles = require 'expcore.roles' --- @dep expcore.roles
local config = require 'config.nukeprotect' --- @dep config.nukeprotect
local move_items_stack = _C.move_items_stack --- @dep expcore.common
local function check_items(player, type)
-- if the player has perms to be ignored, then they should be
if config.ignore_permisison and Roles.player_allowed(player, config.ignore_permisison) then return end
-- if the players
if config.ignore_admins and player.admin then return end
local inventory = player.get_inventory(type)
for i = 1, #inventory do
local item = inventory[i]
if item.valid and item.valid_for_read and config[tostring(type)][item.name] then
player.print({ "nukeprotect.found", { "item-name." .. item.name } })
-- insert the items into the table so all items are transferred at once
move_items_stack({ item })
end
end
end
for _, inventory in ipairs(config.inventories) do
if #inventory.items > 0 then
Event.add(inventory.event, function(event)
local player = game.get_player(event.player_index)
if player and player.valid then
check_items(player, inventory.inventory)
end
end)
end
end
if config.disable_nuke_research then
Event.add(defines.events.on_research_started, function(event)
local name = event.research.name
if config.disable_nuke_research_names[name] then
event.research.force.cancel_current_research()
end
end)
end

View File

@@ -0,0 +1,16 @@
--- Makes polution look much nice of the map, ie not one big red mess
-- @addon Pollution-Grading
local Event = require 'utils.event' --- @dep utils.event
local config = require 'config.pollution_grading' --- @dep config.pollution_grading
local delay = config.update_delay * 3600 -- convert from minutes to ticks
Event.on_nth_tick(delay, function()
local surface = game.surfaces[1]
local true_max = surface.get_pollution(config.reference_point)
local max = true_max*config.max_scalar
local min = max*config.min_scalar
local settings = game.map_settings.pollution
settings.expected_max_per_chunk = max
settings.min_to_show_per_chunk = min
end)

View File

@@ -0,0 +1,40 @@
--- When a player triggers protection multiple times they are automatically jailed
-- @addon protection-jail
local Event = require 'utils.event' ---@dep utils.event
local Global = require 'utils.global' ---@dep utils.global
local Jail = require 'modules.control.jail' ---@dep modules.control.jail
local Protection = require 'modules.control.protection' --- @dep modules.control.protection
local format_chat_player_name = _C.format_chat_player_name --- @dep expcore.common
--- Stores how many times the repeat violation was triggered
local repeat_count = {}
Global.register(repeat_count, function(tbl)
repeat_count = tbl
end)
--- When a protection is triggered increment their counter and jail if needed
Event.add(Protection.events.on_repeat_violation, function(event)
local player = game.get_player(event.player_index)
-- Increment the counter
if repeat_count[player.index] then
repeat_count[player.index] = repeat_count[player.index] + 1
else
repeat_count[player.index] = 1
end
-- Jail if needed
if repeat_count[player.index] < 3 then
return
end
local player_name_color = format_chat_player_name(player)
Jail.jail_player(player, '<protection>', 'Removed too many protected entities, please wait for a moderator.')
game.print{'protection-jail.jail', player_name_color}
end)
--- Clear the counter when they leave the game (stops a build up of data)
Event.add(defines.events.on_player_left_game, function(event)
repeat_count[event.player_index] = nil
end)

View File

@@ -0,0 +1,28 @@
--- 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
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)
-- player less than 30 min
if (Reports.count_reports(player) > 1) and (total_playtime > math.max(player.online_time * 2, 108000)) then
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
end)

View File

@@ -0,0 +1,138 @@
--- When a player walks around the tiles under them will degrade over time, the same is true when entites are built
-- @addon Scorched-Earth
local Event = require 'utils.event' --- @dep utils.event
local Global = require 'utils.global' --- @dep utils.global
local print_grid_value, clear_flying_text = _C.print_grid_value, _C.clear_flying_text --- @dep expcore.common
local config = require 'config.scorched_earth' --- @dep config.scorched_earth
-- Loops over the config and finds the wile which has the highest value for strength
local max_strength = 0
for _, strength in pairs(config.strengths) do
if strength > max_strength then
max_strength = strength
end
end
-- Used for debugging the degrade chances
local debug_players = {}
Global.register(debug_players, function(tbl)
debug_players = tbl
end)
-- Will degrade a tile down to the next tile when called
local function degrade(surface, position)
local tile = surface.get_tile(position)
local tile_name = tile.name
local degrade_tile_name = config.degrade_order[tile_name]
if not degrade_tile_name then return end
surface.set_tiles{{name=degrade_tile_name, position=position}}
end
-- Same as degrade but will degrade all tiles that are under an entity
local function degrade_entity(entity)
local surface = entity.surface
local position = entity.position
local tiles = {}
if not config.entities[entity.name] then return end
local box = entity.prototype.collision_box
local lt = box.left_top
local rb = box.right_bottom
for x = lt.x, rb.x do -- x loop
local px = position.x+x
for y = lt.y, rb.y do -- y loop
local p = {x=px, y=position.y+y}
local tile = surface.get_tile(p)
local tile_name = tile.name
local degrade_tile_name = config.degrade_order[tile_name]
if not degrade_tile_name then return end
table.insert(tiles, {name=degrade_tile_name, position=p})
end
end
surface.set_tiles(tiles)
end
-- Turns the strength of a tile into a probability (0 = impossible, 1 = certain)
local function get_probability(strength)
local v1 = strength/max_strength
local dif = 1 - v1
local v2 = dif/2
return (1-v1+v2)/config.weakness_value
end
-- Gets the mean of the strengths around a tile to give the strength at that position
local function get_tile_strength(surface, position)
local tile = surface.get_tile(position)
local tile_name = tile.name
local strength = config.strengths[tile_name]
if not strength then return end
for x = -1, 1 do -- x loop
local px = position.x + x
for y = -1, 1 do -- y loop
local check_tile = surface.get_tile{x=px, y=position.y+y}
local check_tile_name = check_tile.name
local check_strength = config.strengths[check_tile_name] or 0
strength = strength + check_strength
end
end
return strength/9
end
-- Same as get_tile_strength but returns to a in game text rather than as a value
local function debug_get_tile_strength(surface, position)
for x = -3, 3 do -- x loop
local px = position.x+x
for y = -3, 3 do -- y loop
local p = {x=px, y=position.y+y}
local strength = get_tile_strength(surface, p) or 0
local tile = surface.get_tile(p)
print_grid_value(get_probability(strength)*config.weakness_value, surface, tile.position)
end
end
end
-- When the player changes position the tile will have a chance to downgrade, debug check is here
Event.add(defines.events.on_player_changed_position, function(event)
local player = game.players[event.player_index]
local surface = player.surface
local position = player.position
local strength = get_tile_strength(surface, position)
if not strength then return end
if get_probability(strength) > math.random() then
degrade(surface, position)
end
if debug_players[player.name] then
debug_get_tile_strength(surface, position)
end
end)
-- When an entity is build there is a much higher chance that the tiles will degrade
Event.add(defines.events.on_built_entity, function(event)
local entity = event.created_entity
local surface = entity.surface
local position = entity.position
local strength = get_tile_strength(surface, position)
if not strength then return end
if get_probability(strength)*config.weakness_value > math.random() then
degrade_entity(entity)
end
end)
-- Same as above but with robots
Event.add(defines.events.on_robot_built_entity, function(event)
local entity = event.created_entity
local surface = entity.surface
local position = entity.position
local strength = get_tile_strength(surface, position)
if not strength then return end
if get_probability(strength)*config.weakness_value > math.random() then
degrade_entity(entity)
end
end)
-- Used as a way to access the global table
return function(player_name, state)
local player = game.players[player_name]
clear_flying_text(player.surface)
debug_players[player_name] = state
end

View File

@@ -0,0 +1,236 @@
--- Adds a custom spawn area with chests and afk turrets
-- @addon Spawn-Area
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 turrets = config.turrets.locations
Global.register(turrets, function(tbl)
turrets = tbl
end)
-- 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
force = game.create_force('spawn')
force.set_cease_fire('player', true)
-- force.set_friend('player', true)
game.forces['player'].set_cease_fire('spawn', true)
-- game.forces['player'].set_friend('spawn', true)
return force
end
-- 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
entity.minable = false
entity.rotatable = false
entity.operable = false
if set_force then
entity.force = get_spawn_force()
end
end
end
-- Will spawn all infinite ammo turrets and keep them refilled
local function spawn_turrets()
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)
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 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(config.afk_belts.locations) do
local set_position = apply_offset(position, belt_set)
for _, belt in pairs(belt_details) do
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 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 = {}
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 tr = config.spawn_area.tile_radius
local tr2 = tr^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 < tr2 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
local function spawn_resource_tiles(surface)
for _, v in ipairs(config.resource_tiles.resources) do
if v.enabled then
for x=v.offset[1], v.offset[1] + v.size[1] do
for y=v.offset[2], v.offset[2] + v.size[2] do
surface.create_entity({name=v.name, amount=v.amount, position={x, y}})
end
end
end
end
end
local function spawn_resource_patches(surface)
for _, v in ipairs(config.resource_patches.resources) do
if v.enabled then
for i=1, v.num_patches do
surface.create_entity({name=v.name, amount=v.amount, position={v.offset[1] + v.offset_next[1] * (i - 1), v.offset[2] + v.offset_next[2] * (i - 1)}})
end
end
end
end
-- 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
if config.resource_refill_nearby.enabled then
-- could have a flag in global that early returns if true, and reset it on_tick
Event.on_nth_tick(36000, function()
if game.tick < 10 then
return
end
for _, ore in pairs(game.players[1].surface.find_entities_filtered{position=game.players[1].force.get_spawn_position(game.players[1].surface), radius=config.resource_refill_nearby.range, name=config.resource_refill_nearby.resources_name}) do
ore.amount = ore.amount + math.random(config.resource_refill_nearby.amount[1], config.resource_refill_nearby.amount[2])
end
end)
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
get_spawn_force()
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
if config.resource_tiles.enabled then spawn_resource_tiles(s) end
if config.resource_patches.enabled then spawn_resource_patches(s) end
player.teleport(p, s)
end)
-- Way to access global table
return turrets

View File

@@ -0,0 +1,91 @@
---LuaPlayerBuiltEntityEventFilters
---Events.set_event_filter(defines.events.on_built_entity, {{filter = "name", name = "fast-inserter"}})
local Event = require 'utils.event' --- @dep utils.event
local config = require 'config.station_auto_name' --- @dep config.chat_reply
--Credit to Cooldude2606 for using his lua magic to make this function.
local directions = {
['W'] = -0.875,
['NW'] = -0.625,
['N'] = -0.375,
['NE'] = -0.125,
['E'] = 0.125,
['SE'] = 0.375,
['S'] = 0.625,
['SW'] = 0.875
}
local function Angle(entity)
local angle = math.atan2(entity.position.y, entity.position.x)/math.pi
for direction, requiredAngle in pairs(directions) do
if angle < requiredAngle then
return direction
end
end
return 'W'
end
local custom_string = ' *'
local custom_string_len = #custom_string
local function station_name_changer(event)
local entity = event.created_entity
local name = entity.name
if name == "entity-ghost" then
if entity.ghost_name ~= "train-stop" then return end
local backername = entity.backer_name
if backername ~= '' then
entity.backer_name = backername..custom_string
end
elseif name == "train-stop" then --only do the event if its a train stop
local backername = entity.backer_name
if backername:sub(-custom_string_len) == custom_string then
entity.backer_name = backername:sub(1, -custom_string_len-1)
return
end
local boundingBox = entity.bounding_box
-- expanded box for recourse search:
local bounding2 = {{boundingBox.left_top.x-100 ,boundingBox.left_top.y-100} , {boundingBox.right_bottom.x+100, boundingBox.right_bottom.y+100 }}
-- gets all resources in bounding_box2:
local recourses = game.surfaces[1].find_entities_filtered{area = bounding2, type = "resource"}
if #recourses > 0 then -- save cpu time if their are no recourses in bounding_box2
local closest_distance
local px, py = boundingBox.left_top.x, boundingBox.left_top.y
local recourse_closed
--Check which recourse is closest
for i, item in ipairs(recourses) do
local dx, dy = px - item.bounding_box.left_top.x, py - item.bounding_box.left_top.y
local distance = (dx*dx)+(dy*dy)
if not closest_distance or distance < closest_distance then
recourse_closed = item
closest_distance = distance
end
end
local item_name = recourse_closed.name
if item_name then -- prevent errors if something went wrong
local item_name2 = item_name:gsub("^%l", string.upper):gsub('-', ' ') -- removing the - and making first letter capital
local item_type = 'item'
if item_name == 'crude-oil' then
item_type = 'fluid'
end
entity.backer_name = config.station_name:gsub('__icon__', '[img=' .. item_type .. '.' .. item_name .. ']')
:gsub('__item_name__', item_name2)
:gsub('__backer_name__', entity.backer_name)
:gsub('__direction__', Angle(entity))
:gsub('__x__', math.floor(entity.position.x))
:gsub('__y__', math.floor(entity.position.y))
end
end
end
end
-- Add handler to robot and player build entities
Event.add(defines.events.on_built_entity, station_name_changer)
Event.add(defines.events.on_robot_built_entity, station_name_changer)

View File

@@ -0,0 +1,125 @@
--- Makes trees which are marked for decon "decay" quickly to allow faster building
-- @addon Tree-Decon
local Event = require 'utils.event' --- @dep utils.event
local Global = require 'utils.global' --- @dep utils.global
local Roles = require 'expcore.roles' --- @dep expcore.roles
local Gui = require 'expcore.gui' --- @dep expcore.gui
local PlayerData = require 'expcore.player_data' --- @dep expcore.player_data
-- Global queue used to store trees that need to be removed, also cache for player roles
local cache = {}
local tree_queue = { _head=0 }
Global.register({tree_queue, cache}, function(tbl)
tree_queue = tbl[1]
cache = tbl[2]
end)
local function get_permission(player_index)
if cache[player_index] == nil then
local player = game.players[player_index]
if Roles.player_allowed(player, 'fast-tree-decon') then cache[player_index] = 'fast'
elseif Roles.player_allowed(player, 'standard-decon') then cache[player_index] = 'standard'
else cache[player_index] = player.force end
end
return cache[player_index]
end
-- Left menu button to toggle between fast decon and normal decon marking
local HasEnabledDecon = PlayerData.Settings:combine('HasEnabledDecon')
HasEnabledDecon:set_default(false)
Gui.toolbar_toggle_button("entity/tree-01", {'tree-decon.main-tooltip'}, function (player)
return Roles.player_allowed(player, "fast-tree-decon")
end)
:on_event(Gui.events.on_toolbar_button_toggled, function(player, _, event)
HasEnabledDecon:set(player, event.state)
player.print{'tree-decon.toggle-msg', event.state and {'tree-decon.enabled'} or {'tree-decon.disabled'}}
end)
-- Add trees to queue when marked, only allows simple entities and for players with role permission
Event.add(defines.events.on_marked_for_deconstruction, function(event)
-- Check which type of decon a player is allowed
local index = event.player_index
if not index then return end
-- Check what should happen to this entity
local entity = event.entity
if not entity or not entity.valid then return end
-- Not allowed to decon this entity
local last_user = entity.last_user
local allow = get_permission(index)
if last_user and allow ~= 'standard' and allow ~= 'fast' then
entity.cancel_deconstruction(allow)
return
end
-- Allowed to decon this entity, but not fast
if allow ~= 'fast' then return end
local player = game.get_player(index)
if not HasEnabledDecon:get(player) then return end
-- Allowed fast decon on this entity, just trees
local head = tree_queue._head + 1
if not last_user and entity.type ~= 'cliff' then
tree_queue[head] = entity
tree_queue._head = head
end
end)
-- Remove trees at random till the queue is empty
Event.add(defines.events.on_tick, function()
local head = tree_queue._head
if head == 0 then return end
local max_remove = math.floor(head/100)+1
local remove_count = math.random(0, max_remove)
while remove_count > 0 and head > 0 do
local remove_index = math.random(1, head)
local entity = tree_queue[remove_index]
tree_queue[remove_index] = tree_queue[head]
head = head - 1
if entity and entity.valid then
remove_count = remove_count - 1
entity.destroy()
end
end
tree_queue._head = head
end)
-- Clear the cache
Event.on_nth_tick(300, function()
for key, _ in pairs(cache) do
cache[key] = nil
end
end)
-- Clear trees when hit with a car
Event.add(defines.events.on_entity_damaged, function(event)
if not (event.damage_type.name == 'impact' and event.force) then
return
end
if not (event.entity.type == 'tree' or event.entity.type == 'simple-entity') then
return
end
if (not event.cause) or (event.cause.type ~= 'car')then
return
end
local driver = event.cause.get_driver()
if not driver then return end
local allow = get_permission(driver.player.index)
if allow == "fast" and HasEnabledDecon:get(driver.player) then
event.entity.destroy()
else
event.entity.order_deconstruction(event.force, driver.player)
end
end)

View File

@@ -0,0 +1,27 @@
--[[-- Commands Module - Admin Chat
- Adds a command that allows admins to talk in a private chat
@commands Admin-Chat
]]
local Commands = require 'expcore.commands' --- @dep expcore.commands
local format_chat_player_name = _C.format_chat_player_name --- @dep expcore.common
require 'config.expcore.command_general_parse'
--- Sends a message in chat that only admins can see
-- @command admin-chat
-- @tparam string message the message to send in the admin chat
Commands.new_command('admin-chat', {'expcom-admin-chat.description'}, 'Sends a message in chat that only admins can see.')
:add_param('message', false)
:enable_auto_concat()
:set_flag('admin_only')
:add_alias('ac')
:register(function(player, message)
local player_name_colour = format_chat_player_name(player)
for _, return_player in pairs(game.connected_players) do
if return_player.admin then
return_player.print{'expcom-admin-chat.format', player_name_colour, message}
end
end
return Commands.success -- prevents command complete message from showing
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', {'expcom-admin-marker.description'}, '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

@@ -0,0 +1,84 @@
--[[-- Commands Module - Artillery
- Adds a command that help shot artillery
@commands Artillery
]]
local Commands = require 'expcore.commands' --- @dep expcore.commands
require 'config.expcore.command_general_parse'
local Selection = require 'modules.control.selection' --- @dep modules.control.selection
local SelectionArtyArea = 'ArtyArea'
local function location_break(player, pos)
if player.force.is_chunk_charted(player.surface, {x=math.floor(pos.left_top.x / 32), y=math.floor(pos.left_top.y / 32)}) then
return true
elseif player.force.is_chunk_charted(player.surface, {x=math.floor(pos.left_top.x / 32), y=math.floor(pos.right_bottom.y / 32)}) then
return true
elseif player.force.is_chunk_charted(player.surface, {x=math.floor(pos.right_bottom.x / 32), y=math.floor(pos.left_top.y / 32)}) then
return true
elseif player.force.is_chunk_charted(player.surface, {x=math.floor(pos.right_bottom.x / 32), y=math.floor(pos.right_bottom.y / 32)}) then
return true
else
return false
end
end
--- align an aabb to the grid by expanding it
local function aabb_align_expand(aabb)
return {
left_top = {x = math.floor(aabb.left_top.x), y = math.floor(aabb.left_top.y)},
right_bottom = {x = math.ceil(aabb.right_bottom.x), y = math.ceil(aabb.right_bottom.y)}
}
end
--- when an area is selected to add protection to the area
Selection.on_selection(SelectionArtyArea, function(event)
local area = aabb_align_expand(event.area)
local player = game.get_player(event.player_index)
if player == nil then
return
end
if not (game.players[event.player_index].cheat_mode or location_break(player, event.area)) then
return Commands.error
end
local count = 0
local hit = {}
for _, e in pairs(player.surface.find_entities_filtered({area=area, type={'unit-spawner', 'turret'}, force='enemy'})) do
local skip = false
for _, pos in ipairs(hit) do
if math.sqrt(math.abs(e.position.x - pos.x) ^ 2 + math.abs(e.position.y - pos.y) ^ 2) < 6 then
skip = true
break
end
end
if not skip then
player.surface.create_entity{name='artillery-flare', position=e.position, force=player.force, life_time=240, movement={0, 0}, height=0, vertical_speed=0, frame_speed=0}
table.insert(hit, e.position)
count = count + 1
if count > 400 then
break
end
end
end
end)
Commands.new_command('artillery-target-remote', {'expcom-artillery.description'}, 'Artillery Target Remote')
:register(function(player)
if Selection.is_selecting(player, SelectionArtyArea) then
Selection.stop(player)
else
Selection.start(player, SelectionArtyArea)
end
return Commands.success
end)

View File

@@ -0,0 +1,25 @@
--[[-- Commands Module - Bot queue
- Adds a command that allows changing bot queue
@commands Bot Queue
]]
local Commands = require 'expcore.commands' --- @dep expcore.commands
require 'config.expcore.command_general_parse'
Commands.new_command('bot-queue-get', {'expcom-bot-queue.description-get'}, 'Get bot queue')
:set_flag('admin_only')
:register(function(player)
local s = player.force.max_successful_attempts_per_tick_per_construction_queue
local f = player.force.max_failed_attempts_per_tick_per_construction_queue
return Commands.success{'expcom-bot-queue.result', player.name, s, f}
end)
Commands.new_command('bot-queue-set', {'expcom-bot-queue.description-set'}, 'Set bot queue')
:add_param('amount', 'integer-range', 1, 20)
:set_flag('admin_only')
:register(function(player, amount)
player.force.max_successful_attempts_per_tick_per_construction_queue = 3 * amount
player.force.max_failed_attempts_per_tick_per_construction_queue = 1 * amount
game.print{'expcom-bot-queue.result', player.name, 3 * amount, 1 * amount}
return Commands.success
end)

View File

@@ -0,0 +1,45 @@
--[[-- Commands Module - Cheat Mode
- Adds a command that allows players to enter cheat mode
@commands Cheat-Mode
]]
local Commands = require 'expcore.commands' --- @dep expcore.commands
require 'config.expcore.command_general_parse'
--- Toggles cheat mode for your player, or another player.
-- @command toggle-cheat-mode
-- @tparam[opt=self] LuaPlayer player player to toggle chest mode of, can be nil for self
Commands.new_command('toggle-cheat-mode', {'expcom-cheat.description-cheat'}, 'Toggles cheat mode for your player, or another player.')
:add_param('player', true, 'player')
:set_defaults{player=function(player)
return player -- default is the user using the command
end}
:set_flag('admin_only')
:register(function(_, player)
player.cheat_mode = not player.cheat_mode
return Commands.success
end)
Commands.new_command('research-all', {'expcom-cheat.description-res'}, 'Set all research for your force.')
:set_flag('admin_only')
:add_param('force', true, 'force')
:set_defaults{force=function(player)
return player.force
end}
:register(function(player, force)
force.research_all_technologies()
game.print{'expcom-cheat.res', player.name}
return Commands.success
end)
Commands.new_command('toggle-always-day', {'expcom-cheat.description-day'}, 'Toggles always day in surface.')
:set_flag('admin_only')
:add_param('surface', true, 'surface')
:set_defaults{surface=function(player)
return player.surface
end}
:register(function(player, surface)
surface.always_day = not surface.always_day
game.print{'expcom-cheat.day', player.name, surface.always_day}
return Commands.success
end)

View File

@@ -0,0 +1,23 @@
--[[-- Commands Module - Clear Inventory
- Adds a command that allows admins to clear people's inventorys
@commands Clear-Inventory
]]
local Commands = require 'expcore.commands' --- @dep expcore.commands
local move_items_stack = _C.move_items_stack --- @dep expcore.common
require 'config.expcore.command_role_parse'
--- Clears a players inventory
-- @command clear-inventory
-- @tparam LuaPlayer player the player to clear the inventory of
Commands.new_command('clear-inventory', {'expcom-clr-inv.description'}, 'Clears a players inventory')
:add_param('player', false, 'player-role')
:add_alias('clear-inv', 'move-inventory', 'move-inv')
:register(function(_, player)
local inv = player.get_main_inventory()
if not inv then
return Commands.error{'expcore-commands.reject-player-alive'}
end
move_items_stack(inv)
inv.clear()
end)

View File

@@ -0,0 +1,107 @@
--[[-- Commands Module - Connect
- Adds a commands that allows you to request a player move to another server
@commands Connect
]]
local Async = require 'expcore.async' --- @dep expcore.async
local External = require 'expcore.external' --- @dep expcore.external
local Commands = require 'expcore.commands' --- @dep expcore.commands
require 'config.expcore.command_role_parse'
local concat = table.concat
local request_connection = Async.register(External.request_connection)
local function get_server_id(server)
local current_server = External.get_current_server()
local current_version = current_server.version
local servers = External.get_servers_filtered(server)
local server_names_before, server_names = {}, {}
local server_count_before, server_count = 0, 0
for next_server_id, server_details in pairs(servers) do
server_count_before = server_count_before + 1
server_names_before[server_count_before] = server_details.name
if server_details.version == current_version then
server_count = server_count + 1
server_names[server_count] = server_details.name
else
servers[next_server_id] = nil
end
end
if server_count > 1 then
return false, Commands.error{'expcom-connect.too-many-matching', concat(server_names, ', ')}
elseif server_count == 1 then
local server_id, server_details = next(servers)
local status = External.get_server_status(server_id)
if server_id == current_server.id then
return false, Commands.error{'expcom-connect.same-server', server_details.name}
elseif status == 'Offline' then
return false, Commands.error{'expcom-connect.offline', server_details.name}
end
return true, server_id
elseif server_count_before > 0 then
return false, Commands.error{'expcom-connect.wrong-version', concat(server_names_before, ', ')}
else
return false, Commands.error{'expcom-connect.none-matching'}
end
end
--- Connect to a different server
-- @command connect
-- @tparam string server The address or name of the server to connect to
-- @tparam[opt=false] boolean is_address If an address was given for the server param
Commands.new_command('connect', {'expcom-connect.description'}, 'Connect to another server')
:add_param('server')
:add_param('is_address', true, 'boolean')
:add_alias('join', 'server')
:register(function(player, server, is_address)
local server_id = server
if not is_address and External.valid() then
local success, new_server_id = get_server_id(server)
if not success then return new_server_id end
server_id = new_server_id
end
Async(request_connection, player, server_id, true)
end)
--- Connect a player to a different server
-- @command connect-player
-- @tparam string address The address or name of the server to connect to
-- @tparam LuaPlayer player The player to connect to a different server
-- @tparam[opt=false] boolean is_address If an address was given for the server param
Commands.new_command('connect-player', {'expcom-connect.description-player'}, 'Send a player to a different server')
:add_param('player', 'player-role')
:add_param('server')
:add_param('is_address', true, 'boolean')
:register(function(_, player, server, is_address)
local server_id = server
if not is_address and External.valid() then
local success, new_server_id = get_server_id(server)
if not success then return new_server_id end
server_id = new_server_id
end
External.request_connection(player, server_id)
end)
--- Connect all players to a different server
-- @command connect-all
-- @tparam string address The address or name of the server to connect to
-- @tparam[opt=false] boolean is_address If an address was given for the server param
Commands.new_command('connect-all', {'expcom-connect.description-all'}, 'Connect all players to another server')
:add_param('server')
:add_param('is_address', true, 'boolean')
:register(function(_, server, is_address)
local server_id = server
if not is_address and External.valid() then
local success, new_server_id = get_server_id(server)
if not success then return new_server_id end
server_id = new_server_id
end
for _, player in pairs(game.connected_players) do
External.request_connection(player, server_id)
end
end)

View File

@@ -0,0 +1,14 @@
--[[-- Commands Module - Debug
- Adds a command that opens the debug frame
@commands Debug
]]
local DebugView = require 'modules.gui.debug.main_view' --- @dep modules.gui.debug.main_view
local Commands = require 'expcore.commands' --- @dep expcore.commands
--- Opens the debug pannel for viewing tables.
-- @command debug
Commands.new_command('debug', {'expcom-debug.description'}, 'Opens the debug pannel for viewing tables.')
:register(function(player)
DebugView.open_dubug(player)
end)

View File

@@ -0,0 +1,29 @@
--[[-- Commands Module - Enemy
- Adds a command of handling enemy
@commands Enemy
]]
local Commands = require 'expcore.commands' --- @dep expcore.commands
require 'config.expcore.command_general_parse'
Commands.new_command('kill-biters', {'expcom-enemy.description-kill'}, 'Kill all biters only')
:set_flag('admin_only')
:register(function(_, _)
game.forces['enemy'].kill_all_units()
return Commands.success
end)
Commands.new_command('remove-biters', {'expcom-enemy.description-remove'}, 'Remove biters and prevent generation')
:set_flag('admin_only')
:add_param('surface', true, 'surface')
:set_defaults{surface=function(player)
return player.surface
end}
:register(function(_, surface)
for _, entity in pairs(surface.find_entities_filtered({force='enemy'})) do
entity.destroy()
end
surface.map_gen_settings.autoplace_controls['enemy-base'].size = 'none'
return Commands.success
end)

View File

@@ -0,0 +1,19 @@
--[[-- Commands Module - Find
- Adds a command that zooms in on the given player
@commands Find
]]
local Commands = require 'expcore.commands' --- @dep expcore.commands
require 'config.expcore.command_general_parse'
--- Find a player on your map.
-- @command find-on-map
-- @tparam LuaPlayer the player to find on the map
Commands.new_command('find-on-map', {'expcom-find.description'}, 'Find a player on your map.')
:add_param('player', false, 'player-online')
:add_alias('find', 'zoom-to')
:register(function(player, action_player)
local position = action_player.position
player.zoom_to_world(position, 1.75)
return Commands.success -- prevents command complete message from showing
end)

View File

@@ -0,0 +1,19 @@
--[[-- Commands Module - Toggle Friendly Fire
- Adds a command that toggle all friendly fire
@commands Toggle Friendly Fire
]]
local Commands = require 'expcore.commands' --- @dep expcore.commands
require 'config.expcore.command_general_parse'
-- For Modded Server Use
Commands.new_command('toggle-friendly-fire', {'expcom-ff.description'}, 'Toggle friendly fire')
:add_param('force', true, 'force')
:set_defaults{force=function(player)
return player.force
end}
:register(function(player, force)
force.friendly_fire = not force.friendly_fire
game.print{'expcom-ff.ff', player.name, force.friendly_fire}
return Commands.success
end)

View File

@@ -0,0 +1,100 @@
--[[-- Commands Module - Help
- Adds a better help command that allows searching of descriotions and names
@commands Help
]]
local Commands = require 'expcore.commands' --- @dep expcore.commands
local Global = require 'utils.global' --- @dep utils.global
require 'config.expcore.command_general_parse'
local results_per_page = 5
local search_cache = {}
Global.register(search_cache, function(tbl)
search_cache = tbl
end)
--- Searches for a keyword in all commands you are allowed to use.
-- @command chelp
-- @tparam string keyword the keyword that will be looked for
-- @tparam number page the page of help to view, must be in range of pages
Commands.new_command('search-help', {'expcom-chelp.description'}, 'Searches for a keyword in all commands you are allowed to use.')
:add_alias('chelp', 'shelp', 'commands')
:add_param('keyword', true)
:add_param('page', true, 'integer')
:set_defaults{keyword='', page=1}
:register(function(player, keyword, page)
local player_index = player and player.index or 0
-- if keyword is a number then treat it as page number
if tonumber(keyword) then
page = math.floor(tonumber(keyword))
keyword = ''
end
-- gets a value for pages, might have result in cache
local pages
local found = 0
if search_cache[player_index] and search_cache[player_index].keyword == keyword:lower() then
pages = search_cache[player_index].pages
found = search_cache[player_index].found
else
pages = {{}}
local current_page = 1
local page_count = 0
local commands = Commands.search(keyword, player)
-- loops other all commands returned by search, includes game commands
for _, command_data in pairs(commands) do
-- if the number of results if greater than the number already added then it moves onto a new page
if page_count >= results_per_page then
page_count = 0
current_page = current_page + 1
table.insert(pages, {})
end
-- adds the new command to the page
page_count = page_count + 1
found = found + 1
local alias_format = #command_data.aliases > 0 and {'expcom-chelp.alias', table.concat(command_data.aliases, ', ')} or ''
table.insert(pages[current_page], {
'expcom-chelp.format',
command_data.name,
command_data.description,
command_data.help,
alias_format
})
end
-- adds the result to the cache
search_cache[player_index] = {
keyword=keyword:lower(),
pages=pages,
found=found
}
end
-- print the requested page
keyword = keyword == '' and '<all>' or keyword
Commands.print({'expcom-chelp.title', keyword}, 'cyan')
if pages[page] then
for _, command in pairs(pages[page]) do
Commands.print(command)
end
Commands.print({'expcom-chelp.footer', found, page, #pages}, 'cyan')
else
Commands.print({'expcom-chelp.footer', found, page, #pages}, 'cyan')
return Commands.error{'expcom-chelp.out-of-range', page}
end
-- blocks command complete message
return Commands.success
end)
-- way to access global
return search_cache

View File

@@ -0,0 +1,83 @@
--[[-- Commands Module - Home
- Adds a command that allows setting and teleporting to your home position
@commands Home
]]
local Commands = require 'expcore.commands' --- @dep expcore.commands
local Global = require 'utils.global' --- @dep utils.global
require 'config.expcore.command_general_parse'
local homes = {}
Global.register(homes, function(tbl)
homes = tbl
end)
local function teleport(player, position)
local surface = player.surface
local pos = surface.find_non_colliding_position('character', position, 32, 1)
if not position then return false end
if player.driving then player.driving = false end -- kicks a player out a vehicle if in one
player.teleport(pos, surface)
return true
end
local function floor_pos(position)
return {
x=math.floor(position.x),
y=math.floor(position.y)
}
end
--- Teleports you to your home location
-- @command home
Commands.new_command('home', {'expcom-home.description-home'}, 'Teleports you to your home location')
:register(function(player)
local home = homes[player.index]
if not home or not home[1] then
return Commands.error{'expcom-home.no-home'}
end
local rtn = floor_pos(player.position)
teleport(player, home[1])
home[2] = rtn
Commands.print{'expcom-home.return-set', rtn.x, rtn.y}
end)
--- Sets your home location to your current position
-- @command home-set
Commands.new_command('home-set', {'expcom-home.description-home-set'}, 'Sets your home location to your current position')
:register(function(player)
local home = homes[player.index]
if not home then
home = {}
homes[player.index] = home
end
local pos = floor_pos(player.position)
home[1] = pos
Commands.print{'expcom-home.home-set', pos.x, pos.y}
end)
--- Returns your current home location
-- @command home-get
Commands.new_command('home-get', {'expcom-home.description-home-get'}, 'Returns your current home location')
:register(function(player)
local home = homes[player.index]
if not home or not home[1] then
return Commands.error{'expcom-home.no-home'}
end
local pos = home[1]
Commands.print{'expcom-home.home-get', pos.x, pos.y}
end)
--- Teleports you to previous location
-- @command return
Commands.new_command('return', {'expcom-home.description-return'}, 'Teleports you to previous location')
:register(function(player)
local home = homes[player.index]
if not home or not home[2] then
return Commands.error{'expcom-home.no-return'}
end
local rtn = floor_pos(player.position)
teleport(player, home[2])
home[2] = rtn
Commands.print{'expcom-home.return-set', rtn.x, rtn.y}
end)

View File

@@ -0,0 +1,116 @@
--[[-- Commands Module - Interface
- Adds a command that acts as a direct link to the the active softmod, for debug use
@commands Interface
]]
local Commands = require 'expcore.commands' --- @dep expcore.commands
local Global = require 'utils.global' --- @dep utils.global
-- modules that are loaded into the interface env to be accessed
local interface_modules = {
['Commands'] = Commands,
['output'] = _C.player_return,
['Group'] = 'expcore.permission_groups',
['Roles'] = 'expcore.roles',
['Gui'] = 'expcore.gui',
['Async'] = 'expcore.async',
['Datastore'] = 'expcore.datastore',
['External'] = 'expcore.external'
}
-- loads all the modules given in the above table
for key, value in pairs(interface_modules) do
if type(value) == 'string' then
interface_modules[key] = _C.opt_require(value)
end
end
local interface_env = {} -- used as a persistent sandbox for interface commands
local interface_callbacks = {} -- saves callbacks which can load new values per use
Global.register(interface_env, function(tbl)
interface_env = tbl
end)
--- Adds a static module that can be accessed with the interface
-- @tparam string name The name that the value is assigned to
-- @tparam any value The value that will be accessible in the interface env
-- callback param - player: LuaPlayer - the player who used the command
local function add_interface_module(name, value)
interface_modules[name] = value
end
--- Adds a dynamic value that is calculated when the interface is used
-- @tparam string name The name that the value is assigned to
-- @tparam function callback The function that will be called to get the value
local function add_interface_callback(name, callback)
if type(callback) == 'function' then
interface_callbacks[name] = callback
end
end
--- Internal, this is a meta function for __index when self[key] is nil
local function get_index(_, key)
if interface_env[key] then
return interface_env[key]
elseif interface_modules[key] then
return interface_modules[key]
elseif _G[key] then
return _G[key]
end
end
--- Sends an invocation to be ran and returns the result.
-- @command interface
-- @tparam string invocation the command that will be run
Commands.new_command('interface', {'expcom-interface.description'}, 'Sends an invocation to be ran and returns the result.')
:add_param('invocation', false)
:enable_auto_concat()
:set_flag('admin_only')
:register(function(player, invocation)
-- If the invocation has no white space then prepend return to it
if not invocation:find('%s') and not invocation:find('return') then
invocation = 'return '..invocation
end
-- _env will be the new _ENV that the invocation will run inside of
local _env = setmetatable({}, {
__index = get_index,
__newindex = interface_env
})
-- If the command is ran by a player then load the dynamic values
if player then
for name, callback in pairs(interface_callbacks) do
local _, rtn = pcall(callback, player)
rawset(_env, name, rtn)
end
end
-- Compile the invocation with the custom _env value
local invocation_func, compile_error = load(invocation, 'interface', nil, _env)
if compile_error then return Commands.error(compile_error) end
-- Run the invocation
local success, rtn = pcall(invocation_func)
if not success then
local err = rtn:gsub('%.%.%..-/temp/currently%-playing', '')
return Commands.error(err)
end
return Commands.success(rtn)
end)
-- Adds some basic callbacks for the interface
add_interface_callback('player', function(player) return player end)
add_interface_callback('surface', function(player) return player.surface end)
add_interface_callback('force', function(player) return player.force end)
add_interface_callback('position', function(player) return player.position end)
add_interface_callback('entity', function(player) return player.selected end)
add_interface_callback('tile', function(player) return player.surface.get_tile(player.position) end)
-- Module Return
return {
add_interface_module = add_interface_module,
add_interface_callback = add_interface_callback,
interface_env = interface_env,
clean_stack_trace = function(str) return str:gsub('%.%.%..-/temp/currently%-playing', '') end
}

View File

@@ -0,0 +1,47 @@
--[[-- Commands Module - Jail
- Adds a commands that allow admins to jail and unjail
@commands Jail
]]
local Commands = require 'expcore.commands' --- @dep expcore.commands
local Jail = require 'modules.control.jail' --- @dep modules.control.jail
local format_chat_player_name = _C.format_chat_player_name --- @dep expcore.common
require 'config.expcore.command_role_parse'
--- Puts a player into jail and removes all other roles.
-- @command jail
-- @tparam LuaPlayer player the player that will be jailed
-- @tparam[opt] string reason the reason why the player is being jailed
Commands.new_command('jail', {'expcom-jail.description-jail'}, 'Puts a player into jail and removes all other roles.')
:add_param('player', false, 'player-role')
:add_param('reason', true)
:enable_auto_concat()
:register(function(player, action_player, reason)
reason = reason or 'Non Given.'
local action_player_name_color = format_chat_player_name(action_player)
local by_player_name_color = format_chat_player_name(player)
local player_name = player and player.name or '<server>'
if Jail.jail_player(action_player, player_name, reason) then
game.print{'expcom-jail.give', action_player_name_color, by_player_name_color, reason}
else
return Commands.error{'expcom-jail.already-jailed', action_player_name_color}
end
end)
--- Removes a player from jail.
-- @command unjail
-- @tparam LuaPlayer the player that will be unjailed
Commands.new_command('unjail', {'expcom-jail.description-unjail'}, 'Removes a player from jail.')
:add_param('player', false, 'player-role')
:add_alias('clear-jail', 'remove-jail')
:enable_auto_concat()
:register(function(player, action_player)
local action_player_name_color = format_chat_player_name(action_player)
local by_player_name_color = format_chat_player_name(player)
local player_name = player and player.name or '<server>'
if Jail.unjail_player(action_player, player_name) then
game.print{'expcom-jail.remove', action_player_name_color, by_player_name_color}
else
return Commands.error{'expcom-jail.not-jailed', action_player_name_color}
end
end)

View File

@@ -0,0 +1,36 @@
--[[-- Commands Module - Kill
- Adds a command that allows players to kill them selfs and others
@commands Kill
]]
local Commands = require 'expcore.commands' --- @dep expcore.commands
local Roles = require 'expcore.roles' --- @dep expcore.roles
require 'config.expcore.command_general_parse'
require 'config.expcore.command_role_parse'
--- Kills yourself or another player.
-- @command kill
-- @tparam[opt=self] LuaPlayer player the player to kill, must be alive to be valid
Commands.new_command('kill', {'expcom-kill.description'}, 'Kills yourself or another player.')
:add_param('player', true, 'player-role-alive')
:set_defaults{player=function(player)
-- default is the player unless they are dead
if player.character and player.character.health > 0 then
return player
end
end}
:register(function(player, action_player)
if not action_player then
-- can only be nil if no player given and the user is dead
return Commands.error{'expcom-kill.already-dead'}
end
if player == action_player then
action_player.character.die()
elseif Roles.player_allowed(player, 'command/kill/always') then
action_player.character.die()
else
return Commands.error{'expcore-commands.unauthorized'}
end
end)

View File

@@ -0,0 +1,19 @@
--[[-- Commands Module - Last location
- Adds a command that will return the last location of a player
@commands LastLocation
]]
local Commands = require 'expcore.commands' --- @dep expcore.commands
local format_chat_player_name = _C.format_chat_player_name --- @dep expcore.common
require 'config.expcore.command_general_parse'
--- Get the last location of a player.
-- @command last-location
-- @tparam LuaPlayer player the player that you want a location of
Commands.new_command('last-location', {'expcom-lastlocation.description'}, 'Sends you the last location of a player')
:add_alias('location')
:add_param('player', false, 'player')
:register(function(_, action_player)
local action_player_name_color = format_chat_player_name(action_player)
return Commands.success{'expcom-lastlocation.response', action_player_name_color, string.format('%.1f', action_player.position.x), string.format('%.1f', action_player.position.y)}
end)

View File

@@ -0,0 +1,17 @@
--[[-- Commands Module - Me
- Adds a command that adds * around your message in the chat
@commands Me
]]
local Commands = require 'expcore.commands' --- @dep expcore.commands
--- Sends an action message in the chat
-- @command me
-- @tparam string action the action that follows your name in chat
Commands.new_command('me', {'expcom-me.description'}, 'Sends an action message in the chat')
:add_param('action', false)
:enable_auto_concat()
:register(function(player, action)
local player_name = player and player.name or '<Server>'
game.print(string.format('* %s %s *', player_name, action), player.chat_color)
end)

View File

@@ -0,0 +1,34 @@
--[[-- Commands Module - Pollution Handle
- Adds a command that allows modifying pollution
@commands Pollution Handle
]]
local Commands = require 'expcore.commands' --- @dep expcore.commands
require 'config.expcore.command_general_parse'
Commands.new_command('pollution-clear', {'expcom-pol.description-clr'}, 'Clear pollution')
:set_flag('admin_only')
:add_alias('pol-clr')
:add_param('surface', true, 'surface')
:set_defaults{surface=function(player)
return player.surface
end}
:register(function(player, surface)
surface.clear_pollution()
game.print{'expcom-pol.clr', player.name}
return Commands.success
end)
Commands.new_command('pollution-off', {'expcom-pol.description-off'}, 'Disable pollution')
:set_flag('admin_only')
:add_alias('pol-off')
:register(function(player)
game.map_settings.pollution.enabled = false
for _, v in pairs(game.surfaces) do
v.clear_pollution()
end
game.print{'expcom-pol.off', player.name}
return Commands.success
end)

View File

@@ -0,0 +1,215 @@
--[[-- Commands Module - Protection
- Adds commands that can add and remove protection
@commands Protection
]]
local Event = require 'utils.event' --- @dep utils.event
local Global = require 'utils.global' --- @dep utils.global
local Roles = require 'expcore.roles' --- @dep expcore.roles
local Commands = require 'expcore.commands' --- @dep expcore.commands
local format_chat_player_name = _C.format_chat_player_name --- @dep expcore.common
local EntityProtection = require 'modules.control.protection' --- @dep modules.control.protection
local Selection = require 'modules.control.selection' --- @dep modules.control.selection
local SelectionProtectEntity = 'ProtectEntity'
local SelectionProtectArea = 'ProtectArea'
local renders = {} -- Stores all renders for a player
Global.register({
renders = renders
}, function(tbl)
renders = tbl.renders
end)
--- Test if a point is inside an aabb
local function aabb_point_enclosed(point, aabb)
return point.x >= aabb.left_top.x and point.y >= aabb.left_top.y
and point.x <= aabb.right_bottom.x and point.y <= aabb.right_bottom.y
end
--- Test if an aabb is inside another aabb
local function aabb_area_enclosed(aabbOne, aabbTwo)
return aabb_point_enclosed(aabbOne.left_top, aabbTwo)
and aabb_point_enclosed(aabbOne.right_bottom, aabbTwo)
end
--- Align an aabb to the grid by expanding it
local function aabb_align_expand(aabb)
return {
left_top = {x = math.floor(aabb.left_top.x), y = math.floor(aabb.left_top.y)},
right_bottom = {x = math.ceil(aabb.right_bottom.x), y = math.ceil(aabb.right_bottom.y)}
}
end
--- 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
--- Show a protected entity to a player
local function show_protected_entity(player, entity)
local key = get_entity_key(entity)
if renders[player.index][key] then return end
local rb = entity.selection_box.right_bottom
local render_id = rendering.draw_sprite{
sprite = 'utility/notification',
target = entity,
target_offset = {
(rb.x-entity.position.x)*0.75,
(rb.y-entity.position.y)*0.75
},
x_scale = 2,
y_scale = 2,
surface = entity.surface,
players = { player }
}
renders[player.index][key] = render_id
end
--- Show a protected area to a player
local function show_protected_area(player, surface, area)
local key = get_area_key(area)
if renders[player.index][key] then return end
local render_id = rendering.draw_rectangle{
color = {1, 1, 0, 0.5},
filled = false,
width = 3,
left_top = area.left_top,
right_bottom = area.right_bottom,
surface = surface,
players = { player }
}
renders[player.index][key] = render_id
end
--- Remove a render object for a player
local function remove_render(player, key)
local render = renders[player.index][key]
if render and rendering.is_valid(render) then rendering.destroy(render) end
renders[player.index][key] = nil
end
--- Toggles entity protection selection
-- @command protect-entity
Commands.new_command('protect-entity', {'expcom-protection.description-pe'}, 'Toggles entity protection selection, hold shift to remove protection')
:add_alias('pe')
:register(function(player)
if Selection.is_selecting(player, SelectionProtectEntity) then
Selection.stop(player)
else
Selection.start(player, SelectionProtectEntity)
return Commands.success{'expcom-protection.entered-entity-selection'}
end
end)
--- Toggles area protection selection
-- @command protect-area
Commands.new_command('protect-area', {'expcom-protection.description-pa'}, 'Toggles area protection selection, hold shift to remove protection')
:add_alias('pa')
:register(function(player)
if Selection.is_selecting(player, SelectionProtectArea) then
Selection.stop(player)
else
Selection.start(player, SelectionProtectArea)
return Commands.success{'expcom-protection.entered-area-selection'}
end
end)
--- When an area is selected to add protection to entities
Selection.on_selection(SelectionProtectEntity, function(event)
local player = game.get_player(event.player_index)
for _, entity in ipairs(event.entities) do
EntityProtection.add_entity(entity)
show_protected_entity(player, entity)
end
player.print{'expcom-protection.protected-entities', #event.entities}
end)
--- When an area is selected to remove protection from entities
Selection.on_alt_selection(SelectionProtectEntity, function(event)
local player = game.get_player(event.player_index)
for _, entity in ipairs(event.entities) do
EntityProtection.remove_entity(entity)
remove_render(player, get_entity_key(entity))
end
player.print{'expcom-protection.unprotected-entities', #event.entities}
end)
--- When an area is selected to add protection to the area
Selection.on_selection(SelectionProtectArea, function(event)
local area = aabb_align_expand(event.area)
local areas = EntityProtection.get_areas(event.surface)
local player = game.get_player(event.player_index)
for _, next_area in pairs(areas) do
if aabb_area_enclosed(area, next_area) then
return player.print{'expcom-protection.already-protected'}
end
end
EntityProtection.add_area(event.surface, area)
show_protected_area(player, event.surface, area)
player.print{'expcom-protection.protected-area'}
end)
--- When an area is selected to remove protection from the area
Selection.on_alt_selection(SelectionProtectArea, function(event)
local area = aabb_align_expand(event.area)
local areas = EntityProtection.get_areas(event.surface)
local player = game.get_player(event.player_index)
for _, next_area in pairs(areas) do
if aabb_area_enclosed(next_area, area) then
EntityProtection.remove_area(event.surface, next_area)
player.print{'expcom-protection.unprotected-area'}
remove_render(player, get_area_key(next_area))
end
end
end)
--- When selection starts show all protected entities and protected areas
Event.add(Selection.events.on_player_selection_start, function(event)
if event.selection ~= SelectionProtectEntity and event.selection ~= SelectionProtectArea then return end
local player = game.get_player(event.player_index)
local surface = player.surface
renders[player.index] = {}
-- Show protected entities
local entities = EntityProtection.get_entities(surface)
for _, entity in pairs(entities) do
show_protected_entity(player, entity)
end
-- Show always protected entities by name
if #EntityProtection.protected_entity_names > 0 then
for _, entity in pairs(surface.find_entities_filtered{ name = EntityProtection.protected_entity_names, force = player.force }) do
show_protected_entity(player, entity)
end
end
-- Show always protected entities by type
if #EntityProtection.protected_entity_types > 0 then
for _, entity in pairs(surface.find_entities_filtered{ type = EntityProtection.protected_entity_types, force = player.force }) do
show_protected_entity(player, entity)
end
end
-- Show protected areas
local areas = EntityProtection.get_areas(surface)
for _, area in pairs(areas) do
show_protected_area(player, surface, area)
end
end)
--- When selection ends hide protected entities and protected areas
Event.add(Selection.events.on_player_selection_end, function(event)
if event.selection ~= SelectionProtectEntity and event.selection ~= SelectionProtectArea then return end
for _, id in pairs(renders[event.player_index]) do
if rendering.is_valid(id) then rendering.destroy(id) end
end
renders[event.player_index] = nil
end)
--- When there is a repeat offence print it in chat
Event.add(EntityProtection.events.on_repeat_violation, function(event)
local player_name = format_chat_player_name(event.player_index)
Roles.print_to_roles_higher('Regular', {'expcom-protection.repeat-offence', player_name, event.entity.localised_name, event.entity.position.x, event.entity.position.y})
end)

View File

@@ -0,0 +1,62 @@
--[[-- Commands Module - Rainbow
- Adds a command that prints your message in rainbow font
@commands Rainbow
]]
local Commands = require 'expcore.commands' --- @dep expcore.commands
local format_chat_colour = _C.format_chat_colour --- @dep expcore.common
local function step_component(c1, c2)
if c1 < 0 then
return 0, c2+c1
elseif c1 > 1 then
return 1, c2-c1+1
else
return c1, c2
end
end
local function step_color(color)
color.r, color.g = step_component(color.r, color.g)
color.g, color.b = step_component(color.g, color.b)
color.b, color.r = step_component(color.b, color.r)
color.r = step_component(color.r, 0)
return color
end
local function next_color(color, step)
step = step or 0.1
local new_color = {r=0, g=0, b=0}
if color.b == 0 and color.r ~= 0 then
new_color.r = color.r-step
new_color.g = color.g+step
elseif color.r == 0 and color.g ~= 0 then
new_color.g = color.g-step
new_color.b = color.b+step
elseif color.g == 0 and color.b ~= 0 then
new_color.b = color.b-step
new_color.r = color.r+step
end
return step_color(new_color)
end
--- Sends an rainbow message in the chat
-- @command rainbow
-- @tparam string message the message that will be printed in chat
Commands.new_command('rainbow', {'expcom-rainbow.description'}, 'Sends an rainbow message in the chat')
:add_param('message', false)
:enable_auto_concat()
:register(function(player, message)
local player_name = player and player.name or '<Server>'
local player_color = player and player.color or nil
local color_step = 3/message:len()
if color_step > 1 then color_step = 1 end
local current_color = {r=1, g=0, b=0}
local output = format_chat_colour(player_name..': ', player_color)
output = output..message:gsub('%S', function(letter)
local rtn = format_chat_colour(letter, current_color)
current_color = next_color(current_color, color_step)
return rtn
end)
game.print(output)
end)

View File

@@ -0,0 +1,86 @@
local Commands = require 'expcore.commands'
local function Modules(moduleInventory) -- returns the multiplier of the modules
local effect1 = moduleInventory.get_item_count("productivity-module") -- type 1
local effect2 = moduleInventory.get_item_count("productivity-module-2")-- type 2
local effect3 = moduleInventory.get_item_count("productivity-module-3") -- type 3
local multi = effect1*4+effect2*6+effect3*10
return multi/100+1
end
local function AmountOfMachines(itemsPerSecond, output)
if(itemsPerSecond) then
return itemsPerSecond/output
end
end
Commands.new_command('ratio', {'expcom-ratio.description'}, 'This command will give the input and output ratios of the selected machine. Use the parameter for calculating the machines needed for that amount of items per second.')
:add_param('itemsPerSecond', true, 'number')
:register(function(player, itemsPerSecond)
local machine = player.selected -- selected machine
if not machine then --nil check
return Commands.error{'expcom-ratio.notSelecting'}
end
if machine.type ~= "assembling-machine" and machine.type ~= "furnace" then
return Commands.error{'expcom-ratio.notSelecting'}
end
local recipe = machine.get_recipe() -- recipe
if not recipe then --nil check
return Commands.error{'expcom-ratio.notSelecting'}
end
local items = recipe.ingredients -- items in that recipe
local products = recipe.products -- output items
local amountOfMachines
local moduleInventory = machine.get_module_inventory()--the module Inventory of the machine
local multi = Modules(moduleInventory) --function for the productively modals
if itemsPerSecond then
amountOfMachines = math.ceil( AmountOfMachines(itemsPerSecond, 1/recipe.energy*machine.crafting_speed*products[1].amount*multi)) -- amount of machines
end
if not amountOfMachines then
amountOfMachines = 1 --set to 1 to make it not nil
end
----------------------------items----------------------------
for i, item in ipairs(items) do
local sprite -- string to make the icon work either fluid ore item
if item.type == "item" then
sprite = 'expcom-ratio.item-in'
else
sprite = 'expcom-ratio.fluid-in'
end
local ips = item.amount/recipe.energy*machine.crafting_speed*amountOfMachines --math on the items/fluids per second
Commands.print{sprite, math.round(ips, 3), item.name} -- full string
end
----------------------------products----------------------------
for i, product in ipairs(products) do
local sprite -- string to make the icon work either fluid ore item
if product.type == "item" then
sprite = 'expcom-ratio.item-out'
else
sprite = 'expcom-ratio.fluid-out'
end
local output = 1/recipe.energy*machine.crafting_speed*product.amount*multi --math on the outputs per second
Commands.print {sprite, math.round(output*amountOfMachines, 3), product.name} -- full string
end
if amountOfMachines ~= 1 then
Commands.print{'expcom-ratio.machines', amountOfMachines}
end
end)

View File

@@ -0,0 +1,52 @@
--[[-- Commands Module - Repair
- Adds a command that allows an admin to repair and revive a large area
@commands Repair
]]
local Commands = require 'expcore.commands' --- @dep expcore.commands
local config = require 'config.repair' --- @dep config.repair
require 'config.expcore.command_general_parse'
local max_time_to_live = 4294967295 -- unit32 max
--- Repairs entities on your force around you
-- @command repair
-- @tparam number range the range to repair stuff in, there is a max limit to this
Commands.new_command('repair', {'expcom-repair.description'}, 'Repairs entities on your force around you')
:add_param('range', false, 'integer-range', 1, config.max_range)
:register(function(player, range)
local revive_count = 0
local heal_count = 0
local range2 = range^2
local surface = player.surface
local center = player.position
local area = {{x=center.x-range, y=center.y-range}, {x=center.x+range, y=center.y+range}}
if config.allow_ghost_revive then
local ghosts = surface.find_entities_filtered({area=area, type='entity-ghost', force=player.force})
for _, ghost in pairs(ghosts) do
if ghost.valid then
local x = ghost.position.x-center.x
local y = ghost.position.y-center.y
if x^2+y^2 <= range2 then
if config.allow_blueprint_repair or ghost.time_to_live ~= max_time_to_live then
revive_count = revive_count+1
if not config.disallow[ghost.ghost_name] then ghost.revive() end
end
end
end
end
end
if config.allow_heal_entities then
local entities = surface.find_entities_filtered({area=area, force=player.force})
for _, entity in pairs(entities) do
if entity.valid then
local x = entity.position.x-center.x
local y = entity.position.y-center.y
if entity.health and entity.get_health_ratio() ~= 1 and x^2+y^2 <= range2 then
heal_count = heal_count+1
entity.health = max_time_to_live
end
end
end
end
return Commands.success{'expcom-repair.result', revive_count, heal_count}
end)

View File

@@ -0,0 +1,97 @@
--[[-- Commands Module - Reports
- Adds a commands that allow players to report other players
@commands Reports
]]
local Roles = require 'expcore.roles' --- @dep expcore.roles
local Commands = require 'expcore.commands' --- @dep expcore.commands
local Reports = require 'modules.control.reports' --- @dep modules.control.reports
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
-- @tparam string reason the reason the player is being reported
Commands.new_command('report', {'expcom-report.description-report'}, 'Reports a player and notifies moderators')
:add_param('player', false, function(input, player, reject)
input = Commands.parse('player', input, player, reject)
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
end)
:add_param('reason', false)
:add_alias('report-player')
:enable_auto_concat()
:register(function(player, action_player, reason)
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
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
end)
--- Gets a list of all reports that a player has on them. If no player then lists all players and the number of reports on them.
-- @command get-reports
-- @tparam LuaPlayer player the player to get the report for
Commands.new_command('get-reports', {'expcom-report.description-get-reports'}, 'Gets a list of all reports that a player has on them. If no player then lists all players and the number of reports on them.')
:add_param('player', true, 'player')
:add_alias('reports', 'list-reports')
:register(function(_, player)
if player then
local reports = Reports.get_reports(player)
local player_name_color = format_chat_player_name(player)
Commands.print{'expcom-report.player-report-title', player_name_color}
for player_name, reason in pairs(reports) do
local by_player_name_color = format_chat_player_name(player_name)
Commands.print{'expcom-report.list', by_player_name_color, reason}
end
else
local user_reports = Reports.user_reports
Commands.print{'expcom-report.player-count-title'}
for player_name in pairs(user_reports) do
local player_name_color = format_chat_player_name(player_name)
local report_count = Reports.count_reports(player_name)
Commands.print{'expcom-report.list', player_name_color, report_count}
end
end
end)
--- Clears all reports from a player or just the report from one player.
-- @command clear-reports
-- @tparam LuaPlayer player the player to clear the report(s) from
-- @tparam[opt=all] LuaPlayer from-player remove only the report made by this player
Commands.new_command('clear-reports', {'expcom-report.description-clear-reports'}, 'Clears all reports from a player or just the report from one player.')
:add_param('player', false, 'player')
:add_param('from-player', true, 'player')
:register(function(player, action_player, from_player)
if from_player then
if not Reports.remove_report(action_player, from_player.name, player.name) then
return Commands.error{'expcom-report.not-reported'}
end
else
if not Reports.remove_all(action_player, player.name) then
return Commands.error{'expcom-report.not-reported'}
end
end
local action_player_name_color = format_chat_player_name(action_player)
local by_player_name_color = format_chat_player_name(player)
game.print{'expcom-report.removed', action_player_name_color, by_player_name_color}
end)

View File

@@ -0,0 +1,45 @@
local Global = require 'utils.global' --- @dep utils.global
local Event = require 'utils.event' --- @dep utils.event
local Commands = require 'expcore.commands' --- @dep expcore.commands
local config = require 'config.research' --- @dep config.research
local research = {}
Global.register(research, function(tbl)
research = tbl
end)
local function res_queue(force, by_script)
local res_q = force.research_queue
local res = force.technologies['mining-productivity-4']
if #res_q < config.queue_amount then
for i=1, config.queue_amount - #res_q do
force.add_research(res)
if not (by_script) then
game.print{'expcom-res.inf-q', res.name, res.level + i}
end
end
end
end
Commands.new_command('auto-research', {'expcom-res.description-ares'}, 'Automatically queue up research')
:add_alias('ares')
:register(function(player)
research.res_queue_enable = not research.res_queue_enable
if research.res_queue_enable then
res_queue(player.force, true)
end
game.print{'expcom-res.res', player.name, research.res_queue_enable}
return Commands.success
end)
Event.add(defines.events.on_research_finished, function(event)
if research.res_queue_enable then
if event.research.force.rockets_launched > 0 and event.research.force.technologies['mining-productivity-4'].level > 4 then
res_queue(event.research.force, event.by_script)
end
end
end)

View File

@@ -0,0 +1,83 @@
--[[-- Commands Module - Roles
- Adds a commands that allow interaction with the role system
@commands Roles
]]
local Commands = require 'expcore.commands' --- @dep expcore.commands
local Roles = require 'expcore.roles' --- @dep expcore.roles
local Colours = require 'utils.color_presets' --- @dep utils.color_presets
local format_chat_player_name, format_chat_colour_localized = _C.format_chat_player_name, _C.format_chat_colour_localized
--- Assigns a role to a player
-- @command assign-role
-- @tparam LuaPlayer player the player to assign the role to
-- @tparam string role the name of the role to assign to the player, supports auto complete after enter
Commands.new_command('assign-role', {'expcom-roles.description-assign-role'}, 'Assigns a role to a player')
:add_param('player', false, 'player-role')
:add_param('role', false, 'role')
:set_flag('admin-only')
:add_alias('rpromote', 'assign', 'role', 'add-role')
:register(function(player, action_player, role)
local player_highest = Roles.get_player_highest_role(player)
if player_highest.index < role.index then
Roles.assign_player(action_player, role, player.name)
else
return Commands.error{'expcom-roles.higher-role'}
end
end)
--- Unassigns a role from a player
-- @command unassign-role
-- @tparam LuaPlayer player the player to unassign the role from
-- @tparam string role the name of the role to unassign from the player, supports auto complete after enter
Commands.new_command('unassign-role', {'expcom-roles.description-unassign-role'}, 'Unassigns a role from a player')
:add_param('player', false, 'player-role')
:add_param('role', false, 'role')
:set_flag('admin-only')
:add_alias('rdemote', 'unassign', 'rerole', 'remove-role')
:register(function(player, action_player, role)
local player_highest = Roles.get_player_highest_role(player)
if player_highest.index < role.index then
Roles.unassign_player(action_player, role, player.name)
else
return Commands.error{'expcom-roles.higher-role'}
end
end)
--- Lists all roles in they correct order
-- @command list-roles
-- @tparam[opt=all] LuaPlayer player list only the roles which this player has
Commands.new_command('list-roles', {'expcom-roles.description-list-roles'}, 'Lists all roles in they correct order')
:add_param('player', true, 'player')
:add_alias('lsroles', 'roles')
:register(function(_, player)
local roles = Roles.config.order
local message = {'expcom-roles.list'}
if player then
roles = Roles.get_player_roles(player)
end
for index, role in pairs(roles) do
role = Roles.get_role_from_any(role)
local colour = role.custom_color or Colours.white
local role_name = format_chat_colour_localized(role.name, colour)
if index == 1 then
message = {'expcom-roles.list', role_name}
if player then
local player_name_colour = format_chat_player_name(player)
message = {'expcom-roles.list-player', player_name_colour, role_name}
end
else
message = {'expcom-roles.list-element', message, role_name}
end
end
return Commands.success(message)
end)

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 index <= 5 or value > threshold 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', {'expcom-inv-search.description-ia'}, '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', {'expcom-inv-search.description-ir'}, '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', {'expcom-inv-search.description-i'}, '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', {'expcom-inv-search.description-io'}, '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,69 @@
--[[-- Commands Module - Spawn
- Adds a command that allows players to teleport to their spawn point
@commands Spawn
]]
local Commands = require 'expcore.commands' --- @dep expcore.commands
local Roles = require 'expcore.roles' --- @dep expcore.roles
local function teleport(player)
local surface = player.surface
local spawn = player.force.get_spawn_position(surface)
local position = surface.find_non_colliding_position('character', spawn, 32, 1)
-- return false if no new position
if not position then
return false
end
if player.vehicle then
-- Teleport the entity
local entity = player.vehicle
local goto_position = surface.find_non_colliding_position(entity.name, position, 32, 1)
-- Surface teleport can only be done for players and cars at the moment. (with surface as an peramitor it gives this error)
if entity.type == "car" then
entity.teleport(goto_position, surface)
elseif surface.index == entity.surface.index then
-- Try teleport the entity
if not entity.teleport(goto_position) then
player.driving = false
player.teleport(position, surface)
end
end
else
-- Teleport the player
player.teleport(position, surface)
end
return true
end
--- Teleport to spawn
-- @command go-to-spawn
-- @tparam[opt=self] LuaPlayer player the player to teleport to their spawn point
Commands.new_command('go-to-spawn', {'expcom-spawn.description'}, 'Teleport to spawn')
:add_param('player', true, 'player-role-alive')
:set_defaults{
player=function(player)
if player.connected and player.character and player.character.health > 0 then
return player
end
end
}
:add_alias('spawn', 'tp-spawn')
:register(function(player, action_player)
if not action_player then
return Commands.error{'expcom-spawn.unavailable'}
elseif action_player == player then
if not teleport(player) then
return Commands.error{'expcom-spawn.unavailable'}
end
elseif Roles.player_allowed(player, 'command/go-to-spawn/always') then
if not teleport(action_player) then
return Commands.error{'expcom-spawn.unavailable'}
end
else
return Commands.error{'expcore-commands.unauthorized'}
end
end)

View File

@@ -0,0 +1,35 @@
--[[-- 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', {'expcom-spectate.description-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', {'expcom-spectate.description-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

@@ -0,0 +1,16 @@
--[[-- Commands Module - Set game speed
- Adds a command that allows changing game speed
@commands Set game speed
]]
local Commands = require 'expcore.commands' --- @dep expcore.commands
require 'config.expcore.command_general_parse'
Commands.new_command('game-speed', {'expcom-speed.description'}, 'Set game speed')
:add_param('amount', 'number-range', 0.2, 8)
:set_flag('admin_only')
:register(function(player, amount)
game.speed = math.round(amount, 3)
game.print{'expcom-speed.result', player.name, string.format('%.3f', amount)}
return Commands.success
end)

View File

@@ -0,0 +1,33 @@
--[[-- Commands Module - Clear Item On Ground
- Adds a command that clear item on ground so blueprint can deploy safely
@commands Clear Item On Ground
]]
local copy_items_stack = _C.copy_items_stack --- @dep expcore.common
local Commands = require 'expcore.commands' --- @dep expcore.commands
require 'config.expcore.command_general_parse'
Commands.new_command('clear-item-on-ground', {'expcom-surface-clearing.description-ci'}, 'Clear Item On Ground')
:add_param('range', false, 'integer-range', 1, 1000)
:register(function(player, range)
for _, e in pairs(player.surface.find_entities_filtered{position=player.position, radius=range, name='item-on-ground'}) do
if e.stack then
-- calling move_items_stack(e.stack) will crash to desktop
-- https://forums.factorio.com/viewtopic.php?f=7&t=110322
copy_items_stack{e.stack}
e.stack.clear()
end
end
return Commands.success
end)
Commands.new_command('clear-blueprint', {'expcom-surface-clearing.description-cb'}, 'Clear Blueprint')
:add_param('range', false, 'integer-range', 1, 1000)
:register(function(player, range)
for _, e in pairs(player.surface.find_entities_filtered{position=player.position, radius=range, type='entity-ghost'}) do
e.destroy()
end
return Commands.success
end)

View File

@@ -0,0 +1,93 @@
--[[-- Commands Module - Teleport
- Adds a command that allows players to teleport to other players
@commands Teleport
]]
local Commands = require 'expcore.commands' --- @dep expcore.commands
require 'config.expcore.command_general_parse'
local function teleport(from_player, to_player)
local surface = to_player.surface
local position = surface.find_non_colliding_position('character', to_player.position, 32, 1)
-- return false if no new position
if not position then
return false
end
if from_player.vehicle then
-- Teleport the entity
local entity = from_player.vehicle
local goto_position = surface.find_non_colliding_position(entity.name, position, 32, 1)
-- Surface teleport can only be done for players and cars at the moment. (with surface as an peramitor it gives this error)
if entity.type == "car" then
entity.teleport(goto_position, surface)
elseif surface.index == entity.surface.index then
-- Try teleport the entity
if not entity.teleport(goto_position) then
from_player.driving = false
from_player.teleport(position, surface)
end
end
else
-- Teleport the player
from_player.teleport(position, surface)
end
return true
end
--- Teleports a player to another player.
-- @command teleport
-- @tparam LuaPlayer from_player the player that will be teleported, must be alive
-- @tparam LuaPlayer to_player the player to teleport to, must be online (if dead goes to where they died)
Commands.new_command('teleport', {'expcom-tp.description-tp'}, 'Teleports a player to another player.')
:add_param('from_player', false, 'player-alive')
:add_param('to_player', false, 'player-online')
:add_alias('tp')
:set_flag('admin_only')
:register(function(_, from_player, to_player)
if from_player.index == to_player.index then
-- return if attempting to teleport to self
return Commands.error{'expcom-tp.to-self'}
end
if not teleport(from_player, to_player) then
-- return if the teleport failed
return Commands.error{'expcom-tp.no-position-found'}
end
end)
--- Teleports a player to you.
-- @command bring
-- @tparam LuaPlayer player the player that will be teleported, must be alive
Commands.new_command('bring', {'expcom-tp.description-bring'}, 'Teleports a player to you.')
:add_param('player', false, 'player-alive')
:set_flag('admin_only')
:register(function(player, from_player)
if from_player.index == player.index then
-- return if attempting to teleport to self
return Commands.error{'expcom-tp.to-self'}
end
if not teleport(from_player, player) then
-- return if the teleport failed
return Commands.error{'expcom-tp.no-position-found'}
end
from_player.print('Come here my friend')
end)
--- Teleports you to a player.
-- @command goto
-- @tparam LuaPlayer player the player to teleport to, must be online (if dead goes to where they died)
Commands.new_command('goto', {'expcom-tp.description-goto'}, 'Teleports you to a player.')
:add_param('player', false, 'player-online')
:add_alias('tp-me', 'tpme')
:register(function(player, to_player)
if to_player.index == player.index then
-- return if attempting to teleport to self
return Commands.error{'expcom-tp.to-self'}
end
if not teleport(player, to_player) then
-- return if the teleport failed
return Commands.error{'expcom-tp.no-position-found'}
end
end)

View File

@@ -0,0 +1,23 @@
--[[-- Commands Module - Set Automatic Train
- Adds a command that set all train back to automatic
@commands Set Automatic Train
]]
local Commands = require 'expcore.commands' --- @dep expcore.commands
require 'config.expcore.command_general_parse'
local format_number = require('util').format_number
Commands.new_command('set-trains-to-automatic', {'expcom-train.description'}, 'Set All Trains to Automatic')
:register(function(player)
local count = 0
for _, v in pairs(player.force.get_trains()) do
if v.manual_mode then
count = count + 1
v.manual_mode = false
end
end
game.print{'expcom-train.manual-result', player.name, format_number(count)}
return Commands.success
end)

View File

@@ -0,0 +1,15 @@
--- Adds a virtual layer to store power to save space.
-- @commands Vlayer
local Commands = require 'expcore.commands' --- @dep expcore.commands
require 'config.expcore.command_general_parse'
local vlayer = require 'modules.control.vlayer'
Commands.new_command('vlayer-info', {'vlayer.description-vi'}, 'Vlayer Info')
:register(function(_)
local c = vlayer.get_circuits()
for k, v in pairs(c) do
Commands.print(v .. ' : ' .. k)
end
end)

View File

@@ -0,0 +1,74 @@
--[[-- Commands Module - Warnings
- Adds a commands that allow admins to warn other players
@commands Warnings
]]
local Commands = require 'expcore.commands' --- @dep expcore.commands
local Warnings = require 'modules.control.warnings' --- @dep modules.control.warnings
local format_chat_player_name = _C.format_chat_player_name --- @dep expcore.common
local config = require 'config.warnings' --- @dep config.warnings
require 'config.expcore.command_role_parse'
--- Gives a warning to a player; may lead to automatic script action.
-- @command give-warning
-- @tparam LuaPlayer player the player the will recive a warning
-- @tparam string reason the reason the player is being given a warning
Commands.new_command('give-warning', {'expcom-warnings.description-give'}, 'Gives a warning to a player; may lead to automatic script action.')
:add_param('player', false, 'player-role')
:add_param('reason', false)
:add_alias('warn')
:enable_auto_concat()
:register(function(player, action_player, reason)
Warnings.add_warning(action_player, player.name, reason)
local action_player_name_color = format_chat_player_name(action_player)
local by_player_name_color = format_chat_player_name(player)
game.print{'expcom-warnings.received', action_player_name_color, by_player_name_color, reason}
end)
--- Gets the number of warnings a player has. If no player then lists all players and the number of warnings they have.
-- @command get-warnings
-- @tparam[opt=list] LuaPlayer player the player to get the warning for, if nil all players are listed
Commands.new_command('get-warnings', {'expcom-warnings.description-get'}, 'Gets the number of warnings a player has. If no player then lists all players and the number of warnings they have.')
:add_param('player', true, 'player')
:add_alias('warnings', 'list-warnings')
:register(function(_, player)
if player then
local warnings = Warnings.get_warnings(player)
local script_warnings = Warnings.get_script_warnings(player)
local player_name_color = format_chat_player_name(player)
Commands.print{'expcom-warnings.player', player_name_color, #warnings, #script_warnings, config.temp_warning_limit}
for _, warning in ipairs(warnings) do
Commands.print{'expcom-warnings.player-detail', format_chat_player_name(warning.by_player_name), warning.reason}
end
else
local rtn = {}
local user_script_warnings = Warnings.user_script_warnings
for player_name, warnings in pairs(Warnings.user_warnings:get_all()) do
rtn[player_name] = {#warnings, 0}
end
for player_name, warnings in pairs(user_script_warnings) do
if not rtn[player_name] then
rtn[player_name] = {0, 0}
end
rtn[player_name][2] = #warnings
end
Commands.print{'expcom-warnings.list-title'}
for player_name, warnings in pairs(rtn) do
local player_name_color = format_chat_player_name(player_name)
Commands.print{'expcom-warnings.list', player_name_color, warnings[1], warnings[2], config.temp_warning_limit}
end
end
end)
--- Clears all warnings (and script warnings) from a player
-- @command clear-warnings
-- @tparam LuaPlayer player the player to clear the warnings from
Commands.new_command('clear-warnings', {'expcom-warnings.description-clear'}, 'Clears all warnings (and script warnings) from a player')
:add_param('player', false, 'player')
:register(function(player, action_player)
Warnings.clear_warnings(action_player, player.name)
Warnings.clear_script_warnings(action_player)
local action_player_name_color = format_chat_player_name(action_player)
local by_player_name_color = format_chat_player_name(player)
game.print{'expcom-warnings.cleared', action_player_name_color, by_player_name_color}
end)

View File

@@ -0,0 +1,79 @@
--- Adds a waterfill
-- @commands Waterfill
local Commands = require 'expcore.commands' --- @dep expcore.commands
require 'config.expcore.command_general_parse'
local Selection = require 'modules.control.selection' --- @dep modules.control.selection
local SelectionConvertArea = 'WaterfillConvertArea'
--- Align an aabb to the grid by expanding it
local function aabb_align_expand(aabb)
return {
left_top = {x = math.floor(aabb.left_top.x), y = math.floor(aabb.left_top.y)},
right_bottom = {x = math.ceil(aabb.right_bottom.x), y = math.ceil(aabb.right_bottom.y)}
}
end
Commands.new_command('waterfill', {'expcom-waterfill.description'}, 'Change tile to water')
:register(function(player)
local inv = player.get_main_inventory()
if (inv.get_item_count('cliff-explosives')) == 0 then
return player.print{'expcom-waterfill.waterfill-cliff'}
end
if Selection.is_selecting(player, SelectionConvertArea) then
Selection.stop(player)
else
Selection.start(player, SelectionConvertArea)
return Commands.success{'expcom-waterfill.entered-area-selection'}
end
return Commands.success
end)
--- When an area is selected to add protection to the area
Selection.on_selection(SelectionConvertArea, function(event)
local area = aabb_align_expand(event.area)
local player = game.get_player(event.player_index)
if not player then
return
end
local entities = player.surface.find_entities_filtered{area=area, name='steel-chest'}
if #entities == 0 then
player.print('No steel chest found')
return
end
local tiles_to_make = {}
local inv = player.get_main_inventory()
if not inv then
return
end
local clf_exp = inv.get_item_count('cliff-explosives')
for _, entity in pairs(entities) do
if clf_exp >= 1 then
if entity.get_inventory(defines.inventory.chest).is_empty() then
if (math.floor(player.position.x) ~= math.floor(entity.position.x)) or (math.floor(player.position.y) ~= math.floor(entity.position.y)) then
table.insert(tiles_to_make, {name='water-mud', position=entity.position})
entity.destroy()
else
player.print{'expcom-waterfill.waterfill-distance'}
end
end
clf_exp = clf_exp - 1
inv.remove({name='cliff-explosives', count=1})
else
break
end
end
event.surface.set_tiles(tiles_to_make)
end)

View File

@@ -0,0 +1,120 @@
--[[-- 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.control.jail' --- @dep modules.control.jail
-- This will move 'MrBiter' to the jail role and remove all other roles from them
-- 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 give 'MrBiter' all his roles back and remove him from jail
-- again as above the player name is only used in the event for user feedback
Jail.unjail_player('MrBiter', 'Cooldude2606')
]]
local Roles = require 'expcore.roles' --- @dep expcore.roles
local Game = require 'utils.game' --- @dep utils.game
local valid_player = Game.get_player_from_any
local assign_roles = Roles.assign_player
local unassign_roles = Roles.unassign_player
local has_role = Roles.player_has_role
local get_roles = Roles.get_player_roles
local Jail = {
old_roles = {},
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)
return has_role(player, 'Jail')
end
--- Moves a player to jail and removes all 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.'
if has_role(player, 'Jail') then return end
local roles = get_roles(player)
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 }
unassign_roles(player, roles, by_player_name, nil, true)
assign_roles(player, 'Jail', by_player_name, nil, true)
assign_roles(player, roles, by_player_name, nil, true)
event_emit(Jail.events.on_player_jailed, player, by_player_name, reason)
return true
end
--- Moves a player out of jail and restores all roles previously removed
-- @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
if not has_role(player, 'Jail') then return end
unassign_roles(player, 'Jail', by_player_name, nil, true)
event_emit(Jail.events.on_player_unjailed, player, by_player_name)
return true
end
return Jail

View File

@@ -0,0 +1,233 @@
--[[-- Control Module - Production
- Common functions used to track production of items
@control Production
@alias Production
@usage
-- import the module from the control modules
local Production = require 'modules.control.production' --- @dep modules.control.production
-- This will return the less precise index from the one given
-- this means that one_second will return one_minute or ten_hours will return fifty_hours
-- the other precision work like wise
Production.precision_up(defines.flow_precision_index.one_second)
-- The get production function is used to get production, consumion and net
-- it may be used for any item and with any precision level, use total for total
Production.get_production(game.forces.player, 'iron-plate', defines.flow_precision_index.one_minute)
-- The fluctuations works by compearing recent production with the average over time
-- again any precision may be used, apart from one_thousand_hours as there would be no valid average
Production.get_fluctuations(game.forces.player, 'iron-plate', defines.flow_precision_index.one_minute)
-- ETA is calculated based on what function you use but all share a similar method
-- for production eta it will take current production average given by the precision
-- and work out how many ticks it will require to make the required amount (1000 by default)
Production.get_production_eta(game.forces.player, 'iron-plate', defines.flow_precision_index.one_minute, 250000)
-- Both get_color and format_number are helper functions to help format production stats
-- get_color will return green, orange, red, or grey based on the active_value
-- the passive_value is used when active_value is 0 and can only return orange, red, or grey
Production.get_color(clamp, active_value, passive_value)
]]
local Colors = require 'utils.color_presets' --- @dep utils.color_presets
local format_number = require('util').format_number --- @dep util
local precision_index = defines.flow_precision_index
local Production = {}
--- Precision.
-- Functions which are used to do basic things
-- @section precision
--- Gets the next lesser precision index value, eg 5 seconds -> 1 minute
-- @tparam defines.flow_precision_index precision
-- @treturn[1] defines.flow_precision_index the next precision value
-- @treturn[1] number the multiplicive difference between the values
function Production.precision_up(precision)
if precision == precision_index.five_seconds then return precision_index.one_minute, 60
elseif precision == precision_index.one_minute then return precision_index.ten_minutes, 10
elseif precision == precision_index.ten_minutes then return precision_index.one_hour, 6
elseif precision == precision_index.one_hour then return precision_index.ten_hours, 10
elseif precision == precision_index.ten_hours then return precision_index.fifty_hours, 5
elseif precision == precision_index.fifty_hours then return precision_index.two_hundred_fifty_hours, 5
elseif precision == precision_index.two_hundred_fifty_hours then return precision_index.one_thousand_hours, 4
end
end
--- Gets the next greater precision index value, eg 1 minute -> 5 seconds
-- @tparam defines.flow_precision_index precision
-- @treturn[1] defines.flow_precision_index the next precision value
-- @treturn[1] number the multiplicive difference between the values
function Production.precision_down(precision)
if precision == precision_index.one_minute then return precision_index.five_seconds, 60
elseif precision == precision_index.ten_minutes then return precision_index.one_minute, 10
elseif precision == precision_index.one_hour then return precision_index.ten_minutes, 6
elseif precision == precision_index.ten_hours then return precision_index.one_hour, 10
elseif precision == precision_index.fifty_hours then return precision_index.ten_hours, 5
elseif precision == precision_index.two_hundred_fifty_hours then return precision_index.fifty_hours, 5
elseif precision == precision_index.one_thousand_hours then return precision_index.two_hundred_fifty_hours, 4
end
end
--- Gets the number of tick that precision is given over, eg 1 minute -> 3600 ticks
-- @tparam defines.flow_precision_index precision
-- @treturn number the number of ticks in this time
function Production.precision_ticks(precision)
if precision == precision_index.five_seconds then return 300
elseif precision == precision_index.one_minute then return 3600
elseif precision == precision_index.ten_minutes then return 36000
elseif precision == precision_index.one_hour then return 216000
elseif precision == precision_index.ten_hours then return 2160000
elseif precision == precision_index.fifty_hours then return 10800000
elseif precision == precision_index.two_hundred_fifty_hours then return 54000000
elseif precision == precision_index.one_thousand_hours then return 216000000
end
end
--- Statistics.
-- Functions used to get information about production
-- @section stats
--- Returns the production data for the whole game time
-- @tparam LuaForce force the force to get the data for
-- @tparam string item_name the name of the item that you want the data about
-- @treturn table contains total made, used and net
function Production.get_production_total(force, item_name)
local stats = force.item_production_statistics
local made = stats.get_input_count(item_name) or 0
local used = stats.get_output_count(item_name) or 0
return {
made=made,
used=used,
net=made-used
}
end
--- Returns the production data for the given precision game time
-- @tparam LuaForce force the force to get the data for
-- @tparam string item_name the name of the item that you want the data about
-- @tparam defines.flow_precision_index precision the precision that you want the data given to
-- @treturn table contains made, used and net
function Production.get_production(force, item_name, precision)
local stats = force.item_production_statistics.get_flow_count
local made = stats{name=item_name, input=true, precision_index=precision} or 0
local used = stats{name=item_name, input=false, precision_index=precision} or 0
return {
made=made,
used=used,
net=made-used
}
end
--- Returns the current fluctuation from the average
-- @tparam LuaForce force the force to get the data for
-- @tparam string item_name the name of the item that you want the data about
-- @tparam defines.flow_precision_index precision the precision that you want the data given to
-- @treturn table contains made, used and net
function Production.get_fluctuations(force, item_name, precision)
local precision_up = Production.precision_up(precision)
local current = Production.get_production(force, item_name, precision)
local previous = Production.get_production(force, item_name, precision_up)
return {
made=(current.made/previous.made)-1,
used=(current.used/previous.used)-1,
net=(current.net/previous.net)-1,
}
end
--- Returns the amount of ticks required to produce a certain amount
-- @tparam LuaForce force the force to get the data for
-- @tparam string item_name the name of the item that you want the data about
-- @tparam defines.flow_precision_index precision the precision that you want the data given to
-- @tparam[opt=1000] number required the number of items that are required to be made
-- @treturn number the number of ticks required to produce this ammount of items
function Production.get_production_eta(force, item_name, precision, required)
required = required or 1000
local ticks = Production.precision_ticks(precision)
local production = Production.get_production(force, item_name, precision)
return production.made == 0 and -1 or ticks*required/production.made
end
--- Returns the amount of ticks required to consume a certain amount
-- @tparam LuaForce force the force to get the data for
-- @tparam string item_name the name of the item that you want the data about
-- @tparam defines.flow_precision_index precision the precision that you want the data given to
-- @tparam[opt=1000] number required the number of items that are required to be consumed
-- @treturn number the number of ticks required to consume this ammount of items
function Production.get_consumsion_eta(force, item_name, precision, required)
required = required or 1000
local ticks = Production.precision_ticks(precision)
local production = Production.get_production(force, item_name, precision)
return production.used == 0 and -1 or ticks*required/production.used
end
--- Returns the amount of ticks required to produce but not consume a certain amount
-- @tparam LuaForce force the force to get the data for
-- @tparam string item_name the name of the item that you want the data about
-- @tparam defines.flow_precision_index precision the precision that you want the data given to
-- @tparam[opt=1000] number required the number of items that are required to be made but not used
-- @treturn number the number of ticks required to produce, but not use, this ammount of items
function Production.get_net_eta(force, item_name, precision, required)
required = required or 1000
local ticks = Production.precision_ticks(precision)
local production = Production.get_production(force, item_name, precision)
return production.net == 0 and -1 or ticks*required/production.net
end
--- Formating.
-- Functions used to format production values
-- @section formating
--- Returns a color value based on the value that was given
-- @tparam number cutoff value which separates the different colours
-- @tparam number active_value first value tested, tested against cutoff
-- @tparam number passive_value second value tested, tested against 0 when active is 0
-- @treturn table contains r,g,b keys
function Production.get_color(cutoff, active_value, passive_value)
if active_value > cutoff then
return Colors.light_green
elseif active_value < -cutoff then
return Colors.indian_red
elseif active_value ~= 0 then
return Colors.orange
elseif passive_value and passive_value > 0 then
return Colors.orange
elseif passive_value and passive_value < 0 then
return Colors.indian_red
else
return Colors.grey
end
end
--- Returns three parts used to format a number
-- @tparam number value the value to format
-- @treturn[1] string the sign for the number
-- @treturn[1] string the surfix for any unit used
function Production.format_number(value)
local rtn = format_number(math.round(value, 1), true)
local surfix = rtn:sub(-1)
if value > 0 then
rtn = '+'..rtn
elseif value == 0 and rtn:sub(1, 1) == '-' then
rtn = rtn:sub(2)
end
if not tonumber(surfix) then
return surfix, rtn:sub(1, -2)
else
return '', rtn
end
end
return Production

View File

@@ -0,0 +1,206 @@
--[[-- Control Module - Protection
- Controls protected entities
@control Protection
@alias Protection
]]
local Global = require 'utils.global' --- @dep utils.global
local Event = require 'utils.event' --- @dep utils.event
local config = require '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
-- Require roles if a permission is assigned in the config
local Roles
if config.ignore_permission then
Roles = require 'expcore.roles' --- @dep expcore.roles
end
----- Global 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
Global.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.get_player(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_allowed(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_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

View File

@@ -0,0 +1,225 @@
--[[-- Control Module - Reports
- Adds a way to report players and store report messages.
@control Reports
@alias Reports
@usage
-- import the module from the control modules
local Reports = require 'modules.control.reports' --- @dep modules.control.reports
-- This will place a report on "MrBiter" (must be a valid player) the report will have been made
-- by "Cooldude2606" (must be the player name) with the reason 'Liking biters too much' this can be
-- seen by using Reports.get_report.
Reports.report_player('MrBiter', 'Cooldude2606', 'Liking biters too much') -- true
-- The other get methods can be used to get all the reports on a player or to test if a player is reported.
Reports.get_report('MrBiter', 'Cooldude2606') -- 'Liking biters too much'
-- This will remove the warning on 'MrBiter' (must be a valid player) which was made by 'Cooldude2606'.
Reports.remove_report('MrBiter', 'Cooldude2606') -- true
-- This will remove all the report that have been made against 'MrBiter'. Note that the remove event will
-- be triggered once per report issused.
Reports.remove_all('MrBiter') -- true
]]
local Game = require 'utils.game' --- @dep utils.game
local Global = require 'utils.global' --- @dep utils.global
local valid_player = Game.get_player_from_any
local Reports = {
user_reports={}, -- stores all user reports, global table
events = {
--- When a player is reported
-- @event on_player_reported
-- @tparam number player_index the player index of the player who got reported
-- @tparam string by_player_name the name of the player who made the report
-- @tparam string reason the reason given for the report
on_player_reported = script.generate_event_name(),
--- When a report is removed from a player
-- @event on_report_removed
-- @tparam number player_index the player index of the player who has the report removed
-- @tparam string reported_by_name the name of the player who made the removed report
-- @tparam string removed_by_name the name of the player who removed the report
-- @tparam number batch_count the number of reports 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_report_removed = script.generate_event_name()
}
}
local user_reports = Reports.user_reports
Global.register(user_reports, function(tbl)
Reports.user_reports = tbl
user_reports = Reports.user_reports
end)
--- Getters.
-- Functions used to get information from reports
-- @section get-functions
--- Gets a list of all reports that a player has against them
-- @tparam LuaPlayer player the player to get the report for
-- @treturn table a list of all reports, key is by player name, value is reason
function Reports.get_reports(player)
player = valid_player(player)
if not player then return end
return user_reports[player.name] or {}
end
--- Gets a single report against a player given the name of the player who made the report
-- @tparam LuaPlayer player the player to get the report for
-- @tparam string by_player_name the name of the player who made the report
-- @treturn ?string|nil string is the reason that the player was reported, if the player is not reported
function Reports.get_report(player, by_player_name)
player = valid_player(player)
if not player then return end
if not by_player_name then return end
local reports = user_reports[player.name]
return reports and reports[by_player_name]
end
--- Checks if a player is reported, option to get if reported by a certain player
-- @tparam LuaPlayer player the player to check if reported
-- @tparam[opt] string by_player_name when given will check if reported by this player
-- @treturn boolean if the player has been reported
function Reports.is_reported(player, by_player_name)
player = valid_player(player)
if not player then return end
local reports = user_reports[player.name] or {}
if by_player_name then
return reports[by_player_name] ~= nil
else
return table_size(reports) > 0
end
end
--- Counts the number of reports that a player has aganist them
-- @tparam LuaPlayer player the player to count the reports for
-- @tparam[opt] function custom_count when given this function will be used to count the reports
-- @treturn number the number of reports that the user has
function Reports.count_reports(player, custom_count)
player = valid_player(player)
if not player then return end
local reports = user_reports[player.name] or {}
if custom_count then
local ctn = 0
for by_player_name, reason in pairs(reports) do
ctn = ctn + custom_count(player, by_player_name, reason)
end
return ctn
else
return table_size(reports)
end
end
--- Setters.
-- Functions used to get information from reports
-- @section set-functions
--- Adds a report to a player, each player can only report another player once
-- @tparam LuaPlayer player the player to add the report to
-- @tparam string by_player_name the name of the player that is making the report
-- @tparam[opt='Non given.'] string reason the reason that the player is being reported
-- @treturn boolean whether the report was added successfully
function Reports.report_player(player, by_player_name, reason)
player = valid_player(player)
if not player then return end
local player_name = player.name
if reason == nil or not reason:find("%S") then reason = 'No reason given' end
local reports = user_reports[player_name]
if not reports then
reports = {}
user_reports[player_name] = reports
end
if reports[by_player_name] then
return false
else
reports[by_player_name] = reason
end
script.raise_event(Reports.events.on_player_reported, {
name = Reports.events.on_player_reported,
tick = game.tick,
player_index = player.index,
by_player_name = by_player_name,
reason = reason
})
return true
end
--- Used to emit the report removed event, own function due to repeated use in Report.remove_all
-- @tparam LuaPlayer player the player who is having the report removed from them
-- @tparam string reported_by_name the player who had the report
-- @tparam string removed_by_name the player who is clearing the report
-- @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 report_removed_event(player, reported_by_name, removed_by_name, batch, batch_count)
script.raise_event(Reports.events.on_report_removed, {
name = Reports.events.on_report_removed,
tick = game.tick,
player_index = player.index,
reported_by_name = reported_by_name,
removed_by_name = removed_by_name,
batch_count = batch_count or 1,
batch = batch or 1
})
end
--- Removes a report from a player
-- @tparam LuaPlayer player the player to remove the report from
-- @tparam string reported_by_name the name of the player that made the report
-- @tparam string removed_by_name the name of the player who removed the report
-- @treturn boolean whether the report was removed successfully
function Reports.remove_report(player, reported_by_name, removed_by_name)
player = valid_player(player)
if not player then return end
local reports = user_reports[player.name]
if not reports then
return false
end
local reason = reports[reported_by_name]
if not reason then
return false
end
report_removed_event(player, reported_by_name, removed_by_name)
reports[reported_by_name] = nil
return true
end
--- Removes all reports from a player
-- @tparam LuaPlayer player the player to remove the reports from
-- @tparam string removed_by_name the name of the player who removed the report
-- @treturn boolean whether the reports were removed successfully
function Reports.remove_all(player, removed_by_name)
player = valid_player(player)
if not player then return end
local reports = user_reports[player.name]
if not reports then
return false
end
local ctn, total = 0, #reports
for reported_by_name, _ in pairs(reports) do
ctn = ctn + 1
report_removed_event(player, reported_by_name, removed_by_name, ctn, total)
end
user_reports[player.name] = nil
return true
end
return Reports

View File

@@ -0,0 +1,218 @@
--[[-- Control Module - Rockets
- Stores rocket stats for each force.
@control Rockets
@alias Rockets
@usage
-- import the module from the control modules
local Rockets = require 'modules.control.rockets' --- @dep modules.control.rockets
-- Some basic information is stored for each silo that has been built
-- the data includes: the tick it was built, the rockets launched from it and more
Rockets.get_silo_data(rocket_silo_entity)
-- Some information is also stored for each force
Rockets.get_stats('player')
-- You can get the rocket data for all silos for a force by using get_silos
Rockets.get_silos('player')
-- You can get the launch time for a rocket, meaning what game tick the 50th rocket was launched
Rockets.get_rocket_time('player', 50)
-- The rolling average will work out the time to launch one rocket based on the last X rockets
Rockets.get_rolling_average('player', 10)
]]
local Event = require 'utils.event' --- @dep utils.event
local Global = require 'utils.global' --- @dep utils.global
local config = require 'config.gui.rockets' --- @dep config.rockets
local largest_rolling_avg = 0
for _, avg_over in pairs(config.stats.rolling_avg) do
if avg_over > largest_rolling_avg then
largest_rolling_avg = avg_over
end
end
local Rockets = {
times = {},
stats = {},
silos = {}
}
local rocket_times = Rockets.times
local rocket_stats = Rockets.stats
local rocket_silos = Rockets.silos
Global.register({
rocket_times = rocket_times,
rocket_stats = rocket_stats,
rocket_silos = rocket_silos
}, function(tbl)
Rockets.times = tbl.rocket_times
Rockets.stats = tbl.rocket_stats
Rockets.silos = tbl.rocket_silos
rocket_times = Rockets.times
rocket_stats = Rockets.stats
rocket_silos = Rockets.silos
end)
--- Gets the silo data for a given silo entity
-- @tparam LuaEntity silo the rocket silo entity
-- @treturn table the data table for this silo, contains rockets launch, silo status, and its force
function Rockets.get_silo_data(silo)
local position = silo.position
local silo_name = math.floor(position.x)..':'..math.floor(position.y)
return rocket_silos[silo_name]
end
--- Gets the silo data for a given silo entity
-- @tparam string silo_name the silo name that is stored in its data
-- @treturn table the data table for this silo, contains rockets launch, silo status, and its force
function Rockets.get_silo_data_by_name(silo_name)
return rocket_silos[silo_name]
end
--- Gets the silo entity from its silo name, reverse to get_silo_data
-- @tparam string silo_name the silo name that is stored in its data
-- @treturn LuaEntity the rocket silo entity
function Rockets.get_silo_entity(silo_name)
local data = rocket_silos[silo_name]
return data.entity
end
--- Gets the rocket stats for a force
-- @tparam string force_name the name of the force to get the stats for
-- @treturn table the table of stats for the force
function Rockets.get_stats(force_name)
return rocket_stats[force_name] or {}
end
--- Gets all the rocket silos that belong to a force
-- @tparam string force_name the name of the force to get the silos for
-- @treturn table an array of silo data that all belong to this force
function Rockets.get_silos(force_name)
local rtn = {}
for _, silo_data in pairs(rocket_silos) do
if silo_data.force == force_name then
table.insert(rtn, silo_data)
end
end
return rtn
end
--- Gets the launch time of a given rocket, due to cleaning not all counts are valid
-- @tparam string force_name the name of the force to get the count for
-- @tparam number rocket_number the number of the rocket to get the launch time for
-- @treturn number the game tick that the rocket was lanuched on
function Rockets.get_rocket_time(force_name, rocket_number)
return rocket_times[force_name] and rocket_times[force_name][rocket_number] or nil
end
--- Gets the number of rockets that a force has launched
-- @tparam string force_name the name of the force to get the count for
-- @treturn number the number of rockets that the force has launched
function Rockets.get_rocket_count(force_name)
local force = game.forces[force_name]
return force.rockets_launched
end
--- Gets the total number of rockets launched by all forces
-- @treturn number the total number of rockets launched this game
function Rockets.get_game_rocket_count()
local rtn = 0
for _, force in pairs(game.forces) do
rtn = rtn + force.rockets_launched
end
return rtn
end
--- Gets the rolling average time to launch a rocket
-- @tparam string force_name the name of the force to get the average for
-- @tparam number count the distance to get the rolling average over
-- @treturn number the number of ticks required to launch one rocket
function Rockets.get_rolling_average(force_name, count)
local force = game.forces[force_name]
local rocket_count = force.rockets_launched
if rocket_count == 0 then return 0 end
local last_launch_time = rocket_times[force_name][rocket_count]
local start_rocket_time = 0
if count < rocket_count then
start_rocket_time = rocket_times[force_name][rocket_count-count+1]
rocket_count = count
end
return math.floor((last_launch_time-start_rocket_time)/rocket_count)
end
--- Event used to update the stats and the hui when a rocket is launched
Event.add(defines.events.on_rocket_launched, function(event)
local entity = event.rocket_silo
local silo_data = Rockets.get_silo_data(entity)
local force = event.rocket_silo.force
local force_name = force.name
local rockets_launched = force.rockets_launched
--- Handles updates to the rocket stats
local stats = rocket_stats[force_name]
if not stats then
rocket_stats[force_name] = {}
stats = rocket_stats[force_name]
end
if rockets_launched == 1 then
stats.first_launch = event.tick
stats.fastest_launch = event.tick
elseif event.tick-stats.last_launch < stats.fastest_launch then
stats.fastest_launch = event.tick-stats.last_launch
end
stats.last_launch = event.tick
--- Appends the new rocket into the array
if not rocket_times[force_name] then
rocket_times[force_name] = {}
end
rocket_times[force_name][rockets_launched] = event.tick
local remove_rocket = rockets_launched-largest_rolling_avg
if remove_rocket > 0 and not table.contains(config.milestones, remove_rocket) then
rocket_times[force_name][remove_rocket] = nil
end
--- Adds this 1 to the launch count for this silo
silo_data.launched = silo_data.launched+1
end)
--- When a launch is reiggered it will await reset
Event.add(defines.events.on_rocket_launch_ordered, function(event)
local entity = event.rocket_silo
local silo_data = Rockets.get_silo_data(entity)
silo_data.awaiting_reset = true
end)
--- Adds a silo to the list when it is built
local function on_built(event)
local entity = event.created_entity
if entity.valid and entity.name == 'rocket-silo' then
local force = entity.force
local force_name = force.name
local position = entity.position
local silo_name = math.floor(position.x)..':'..math.floor(position.y)
rocket_silos[silo_name] = {
name=silo_name,
force=force_name,
entity=entity,
launched=0,
awaiting_reset=false,
built=game.tick
}
end
end
Event.add(defines.events.on_built_entity, on_built)
Event.add(defines.events.on_robot_built_entity, on_built)
return Rockets

View File

@@ -0,0 +1,172 @@
--[[-- Control Module - Selection
- Controls players who have a selection planner, mostly event handlers
@control Selection
@alias Selection
]]
local Event = require 'utils.event' --- @dep utils.event
local Global = require 'utils.global' --- @dep utils.global
local Selection = {
events = {
--- When a player enters selection mode
-- @event on_player_selection_start
-- @tparam number player_index the player index of the player who entered selection mode
-- @tparam string selection the name of the selection being made
on_player_selection_start = script.generate_event_name(),
--- When a player leaves selection mode
-- @event on_player_selection_end
-- @tparam number player_index the player index of the player who left selection mode
-- @tparam string selection the name of the selection which ended
on_player_selection_end = script.generate_event_name(),
}
}
local selection_tool = { name='selection-tool' }
local selections = {}
Global.register({
selections = selections
}, function(tbl)
selections = tbl.selections
end)
--- Let a player select an area by providing a selection planner
-- @tparam LuaPlayer player The player to place into selection mode
-- @tparam string selection_name The name of the selection to start, used with on_selection
-- @tparam[opt=false] boolean single_use When true the selection will stop after first use
function Selection.start(player, selection_name, single_use, ...)
if not player or not player.valid then return end
if selections[player.index] then
-- Raise the end event if a selection was already in progress
script.raise_event(Selection.events.on_player_selection_end, {
name = Selection.events.on_player_selection_end,
tick = game.tick,
player_index = player.index,
selection = selections[player.index].name
})
end
-- Set the selection data
selections[player.index] = {
name = selection_name,
arguments = { ... },
single_use = single_use == true,
character = player.character
}
-- Raise the event
script.raise_event(Selection.events.on_player_selection_start, {
name = Selection.events.on_player_selection_start,
tick = game.tick,
player_index = player.index,
selection = selection_name
})
-- Give a selection tool if one is not in use
if player.cursor_stack.is_selection_tool then return end
player.clear_cursor() -- Clear the current item
player.cursor_stack.set_stack(selection_tool)
-- Make a slot to place the selection tool even if inventory is full
if not player.character then return end
player.character_inventory_slots_bonus = player.character_inventory_slots_bonus + 1
player.hand_location = { inventory = defines.inventory.character_main, slot = #player.get_main_inventory() }
end
--- Stop a player selection by removing the selection planner
-- @tparam LuaPlayer player The player to exit out of selection mode
function Selection.stop(player)
if not selections[player.index] then return end
local character = selections[player.index].character
local selection = selections[player.index].name
selections[player.index] = nil
-- Raise the event
script.raise_event(Selection.events.on_player_selection_end, {
name = Selection.events.on_player_selection_end,
tick = game.tick,
player_index = player.index,
selection = selection
})
-- Remove the selection tool
if player.cursor_stack.is_selection_tool then
player.cursor_stack.clear()
else
player.remove_item(selection_tool)
end
-- Remove the extra slot
if character and character == player.character then
player.character_inventory_slots_bonus = player.character_inventory_slots_bonus - 1
player.hand_location = nil
end
end
--- Get the selection arguments for a player
-- @tparam LuaPlayer player The player to get the selection arguments for
function Selection.get_arguments(player)
if not selections[player.index] then return end
return selections[player.index].arguments
end
--- Test if a player is selecting something
-- @tparam LuaPlayer player The player to test
-- @tparam[opt] string selection_name If given will only return true if the selection is this selection
function Selection.is_selecting(player, selection_name)
if selection_name ~= nil then
if not selections[player.index] then return false end
return selections[player.index].name == selection_name
else
return player.cursor_stack.is_selection_tool
end
end
--- Filter on_player_selected_area to this custom selection, appends the selection arguments
-- @tparam string selection_name The name of the selection to listen for
-- @tparam function handler The event handler
function Selection.on_selection(selection_name, handler)
Event.add(defines.events.on_player_selected_area, function(event)
local selection = selections[event.player_index]
if not selection or selection.name ~= selection_name then return end
handler(event, table.unpack(selection.arguments))
end)
end
--- Filter on_player_alt_selected_area to this custom selection, appends the selection arguments
-- @param string selection_name The name of the selection to listen for
-- @param function handler The event handler
function Selection.on_alt_selection(selection_name, handler)
Event.add(defines.events.on_player_alt_selected_area, function(event)
local selection = selections[event.player_index]
if not selection or selection.name ~= selection_name then return end
handler(event, table.unpack(selection.arguments))
end)
end
--- Stop selection if the selection tool is removed from the cursor
Event.add(defines.events.on_player_cursor_stack_changed, function(event)
local player = game.get_player(event.player_index) ---@cast player -nil
if player.cursor_stack.is_selection_tool then return end
Selection.stop(player)
end)
--- Stop selection after an event such as death or leaving the game
local function stop_after_event(event)
local player = game.get_player(event.player_index)
Selection.stop(player)
end
Event.add(defines.events.on_pre_player_left_game, stop_after_event)
Event.add(defines.events.on_pre_player_died, stop_after_event)
--- Stop selection after a single use if single_use was true during Selection.start
local function stop_after_use(event)
if not selections[event.player_index] then return end
if not selections[event.player_index].single_use then return end
stop_after_event(event)
end
Event.add(defines.events.on_player_selected_area, stop_after_use)
Event.add(defines.events.on_player_alt_selected_area, stop_after_use)
return Selection

View File

@@ -0,0 +1,173 @@
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
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.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(definition, parent, target)
Gui.destroy_if_valid(parent[definition.name])
local label = parent.add{
type = 'label',
style = 'heading_1_label',
caption = 'Following '..target.name..'.\nClick here or press esc to stop following.',
name = definition.name
}
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)
:static_name(Gui.unique_static_name)
: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

@@ -0,0 +1,176 @@
--[[-- Control Module - Tasks
- Stores tasks for each force.
@control Tasks
@alias Tasks
@usage-- Making and then editing a new task
local task_id = Tasks.add_task(game.player.force.name, nil, game.player.name)
Tasks.update_task(task_id, 'We need more iron!', game.player.name)
]]
local Datastore = require 'expcore.datastore' --- @dep expcore.datastore
local Global = require 'utils.global' --- @dep utils.global
--- Stores all data for the warp gui
local TaskData = Datastore.connect('TaskData')
TaskData:set_serializer(function(raw_key) return raw_key.task_id end)
local Tasks = {}
-- Global lookup table for force name to task ids
local force_tasks = {_uid=1}
Global.register(force_tasks, function(tbl)
force_tasks = tbl
end)
--- Setters.
-- functions used to created and alter tasks
-- @section setters
--[[-- Add a new task for a force, the task can be placed into a certain position for that force
@tparam string force_name the name of the force to add the task for
@tparam[opt] string player_name the player who added this task, will cause them to be listed under editing
@tparam[opt] string task_title the task title, if not given default is used
@tparam[opt] string task_body the task body, if not given default is used
@treturn string the uid of the task which was created
@usage-- Adding a new task for your force
local task_id = Tasks.add_task(game.player.force.name, game.player.name, nil, nil)
]]
function Tasks.add_task(force_name, player_name, task_title, task_body)
-- Get a new task id
local task_id = tostring(force_tasks._uid)
force_tasks._uid = force_tasks._uid + 1
-- Get the existing tasks for this force
local task_ids = force_tasks[force_name]
if not task_ids then
task_ids = {}
force_tasks[force_name] = task_ids
end
-- Insert the task id into the forces tasks
table.insert(task_ids, task_id)
-- Add the new task to the store
TaskData:set(task_id, {
task_id = task_id,
force_name = force_name,
title = task_title or '',
body = task_body or '',
last_edit_name = player_name or '<server>',
last_edit_time = game.tick,
currently_editing = {}
})
return task_id
end
--[[-- Removes a task and any data that is linked with it
@tparam string task_id the uid of the task which you want to remove
@usage-- Removing a task
Tasks.remove_task(task_id)
]]
function Tasks.remove_task(task_id)
local task = TaskData:get(task_id)
local force_name = task.force_name
table.remove_element(force_tasks[force_name], task_id)
TaskData:remove(task_id)
end
--[[-- Update the message and last edited information for a task
@tparam string task_id the uid of the task to update
@tparam string player_name the name of the player who made the edit
@tparam string task_title the title of the task to update to
@tparam string task_body the body of the task to update to
@usage-- Updating the message for on a task
Task.update_task(task_id, game.player.name, 'We need more iron!', 'Build more iron outposts.')
]]
function Tasks.update_task(task_id, player_name, task_title, task_body)
TaskData:update(task_id, function(_, task)
task.last_edit_name = player_name
task.last_edit_time = game.tick
task.title = task_title
task.body = task_body
end)
end
--[[-- Set the editing state for a player, can be used as a warning or to display a text field
@tparam string task_id the uid of the task that you want to effect
@tparam string player_name the name of the player you want to set the state for
@tparam boolean state the new state to set editing to
@usage-- Setting your editing state to true
Tasks.set_editing(task_id, game.player.name, true)
]]
function Tasks.set_editing(task_id, player_name, state)
TaskData:update(task_id, function(_, task)
task.currently_editing[player_name] = state
end)
end
--[[-- Adds an update handler for when a task is added, removed, or updated
@tparam function handler the handler which is called when a task is updated
@usage-- Add a game print when a task is updated
Tasks.on_update(function(task)
game.print(task.force_name..' now has the task: '..task.message)
end)
]]
function Tasks.on_update(handler)
TaskData:on_update(handler)
end
--- Getters.
-- function used to get information about tasks
-- @section getters
--[[-- Gets the task information that is linked with this id
@tparam string task_id the uid of the task you want to get
@treturn table the task information
@usage-- Getting task information outside of on_update
local task = Tasks.get_task(task_id)
]]
function Tasks.get_task(task_id)
return TaskData:get(task_id)
end
--[[-- Gets all the task ids that a force has
@tparam string force_name the name of the force that you want the task ids for
@treturn table an array of all the task ids
@usage-- Getting the task ids for a force
local task_ids = Tasks.get_force_task_ids(game.player.force.name)
]]
function Tasks.get_force_task_ids(force_name)
return force_tasks[force_name] or {}
end
--[[-- Gets the editing state for a player
@tparam string task_id the uid of the task you want to check
@tparam string player_name the name of the player that you want to check
@treturn boolean weather the player is currently editing this task
@usage-- Check if a player is editing a task or not
local editing = Tasks.get_editing(task_id, game.player.name)
]]
function Tasks.get_editing(task_id, player_name)
local task = TaskData:get(task_id)
return task.currently_editing[player_name]
end
-- Module Return
return Tasks

View File

@@ -0,0 +1,762 @@
--[[-- Control Module - vlayer
- Adds a virtual layer to store power to save space.
@control vlayer
@alias vlayer
]]
local Global = require 'utils.global' --- @dep utils.global
local Event = require 'utils.event' --- @dep utils.event
local config = require 'config.vlayer' --- @dep config.vlayer
local move_items_stack = _C.move_items_stack
local mega = 1000000
local vlayer = {}
local vlayer_data = {
entity_interfaces = {
energy = {},
circuit = {},
storage_input = {},
storage_output = {}
},
properties = {
total_surface_area = 0,
used_surface_area = 0,
production = 0,
discharge = 0,
capacity = 0
},
storage = {
items = {},
power_items = {},
energy = 0,
unallocated = {}
},
surface = table.deep_copy(config.surface)
}
Global.register(vlayer_data, function(tbl)
vlayer_data = tbl
end)
for name, properties in pairs(config.allowed_items) do
properties.modded = false
if properties.power then
vlayer_data.storage.power_items[name] = {
value = properties.fuel_value * 1000000,
count = 0
}
end
end
-- For all modded items, create a config for them
for item_name, properties in pairs(config.modded_items) do
local base_properties = config.allowed_items[properties.base_game_equivalent]
local m = properties.multiplier
config.allowed_items[item_name] = {
starting_value = properties.starting_value or 0,
required_area = base_properties.required_area or 0,
surface_area = (base_properties.surface_area or 0) * m,
production = (base_properties.production or 0) * m,
capacity = (base_properties.capacity or 0) * m,
modded = true
}
end
--- Get all items in storage, do not modify
-- @treturn table a dictionary of all items stored in the vlayer
function vlayer.get_items()
return vlayer_data.storage.items
end
--- Get all unallocated items in storage
-- @treturn table a dictionary of all unallocated items stored in the vlayer
function vlayer.get_unallocated_items()
return vlayer_data.storage.unallocated
end
--- Get all allocated items in storage
-- @treturn table a dictionary of all allocated items stored in the vlayer
function vlayer.get_allocated_items()
local r = {}
for k, v in pairs(vlayer_data.storage.items) do
r[k] = v
if vlayer_data.storage.unallocated[k] then
r[k] = r[k] - vlayer_data.storage.unallocated[k]
end
end
return r
end
--- Get the actual defecit of land
local function get_actual_land_defecit()
local n = vlayer_data.properties.total_surface_area - vlayer_data.properties.used_surface_area
for k, v in pairs(vlayer.get_unallocated_items()) do
n = n - (config.allowed_items[k].required_area * v)
end
return n
end
--- Get interface counts
-- @treturn table a dictionary of the vlayer interface counts
function vlayer.get_interface_counts()
local interfaces = vlayer_data.entity_interfaces
return {
energy = #interfaces.energy,
circuit = #interfaces.circuit,
storage_input = #interfaces.storage_input,
storage_output = #interfaces.storage_output,
}
end
--- Get interfaces
-- @treturn table a dictionary of the vlayer interfaces
function vlayer.get_interfaces()
local interfaces = vlayer_data.entity_interfaces
return {
energy = interfaces.energy,
circuit = interfaces.circuit,
storage_input = interfaces.storage_input,
storage_output = interfaces.storage_output,
}
end
--[[
25,000 / 416 s
昼 208秒 ソーラー効率100%
夕方 83秒 1秒ごとにソーラー発電量が約1.2%ずつ下がり、やがて0%になる
夜 41秒 ソーラー発電量が0%になる
朝方 83秒 1秒ごとにソーラー発電量が約1.2%ずつ上がり、やがて100%になる
(surface.dawn) 0.75 18,750 Day 12,500 208s
0.00 0 Noon
(surface.dusk) 0.25 6,250 Sunset 5,000 83s
(surface.evening) 0.45 11,250 Night 2,500 41s
(surface.morning) 0.55 13,750 Sunrise 5,000 83s
]]
--- Get the power multiplier based on the surface time
local function get_production_multiplier()
local mul = vlayer_data.surface.solar_power_multiplier
local surface = vlayer_data.surface
if surface.always_day then
-- Surface is always day, so full production is used
return mul
end
if surface.darkness then
-- We are using a real surface, our config does not contain 'darkness'
local brightness = 1 - surface.darkness
if brightness >= surface.min_brightness then
return mul * (brightness - surface.min_brightness) / (1 - surface.min_brightness)
else
return 0
end
end
-- Caused by using a set config rather than a surface
local tick = game.tick % surface.ticks_per_day
local daytime = tick / surface.ticks_per_day
surface.daytime = daytime
if daytime <= surface.dusk then -- Noon to Sunset
return mul
elseif daytime <= surface.evening then -- Sunset to Night
return mul * (1 - ((daytime - surface.dusk) / (surface.evening - surface.dusk)))
elseif daytime <= surface.morning then -- Night to Sunrise
return 0
elseif daytime <= surface.dawn then -- Sunrise to Morning
return mul * ((surface.daytime - surface.morning) / (surface.dawn - surface.morning))
else -- Morning to Noon
return mul
end
end
--- Get the sustained power multiplier, this needs improving
local function get_sustained_multiplier()
local mul = vlayer_data.surface.solar_power_multiplier
local surface = vlayer_data.surface
if surface.always_day then
-- Surface is always day, so full production is used
return mul
end
-- For nauvis vanilla: 208s + (1/2 x (83s + 83s))
local day_duration = 1 - surface.dawn + surface.dusk
local sunset_duration = surface.evening - surface.dusk
local sunrise_duration = surface.dawn - surface.morning
return mul * (day_duration + (0.5 * (sunset_duration + sunrise_duration)))
end
--- Internal, Allocate items in the vlayer, this will increase the property values of the vlayer such as production and capacity
-- Does not increment item storage, so should not be called before insert_item unless during init
-- Does not validate area requirements, so checks must be performed before calling this function
-- Accepts negative count for deallocating items
-- @tparam string item_name The name of the item to allocate
-- @tparam number count The count of the item to allocate
function vlayer.allocate_item(item_name, count)
local item_properties = config.allowed_items[item_name]
assert(item_properties, 'Item not allowed in vlayer: ' .. tostring(item_name))
if item_properties.production then
vlayer_data.properties.production = vlayer_data.properties.production + item_properties.production * count
end
if item_properties.capacity then
vlayer_data.properties.capacity = vlayer_data.properties.capacity + item_properties.capacity * count
end
if item_properties.discharge then
vlayer_data.properties.discharge = vlayer_data.properties.discharge + item_properties.discharge * count
end
if item_properties.surface_area then
vlayer_data.properties.total_surface_area = vlayer_data.properties.total_surface_area + item_properties.surface_area * count
end
if item_properties.required_area and item_properties.required_area > 0 then
vlayer_data.properties.used_surface_area = vlayer_data.properties.used_surface_area + item_properties.required_area * count
end
end
-- For all allowed items, setup their starting values, default 0
for item_name, properties in pairs(config.allowed_items) do
vlayer_data.storage.items[item_name] = properties.starting_value or 0
if properties.required_area and properties.required_area > 0 then
vlayer_data.storage.unallocated[item_name] = 0
end
vlayer.allocate_item(item_name, properties.starting_value)
end
--- Insert an item into the vlayer, this will increment its count in storage and allocate it if possible
-- @tparam string item_name The name of the item to insert
-- @tparam number count The count of the item to insert
function vlayer.insert_item(item_name, count)
local item_properties = config.allowed_items[item_name]
assert(item_properties, 'Item not allowed in vlayer: ' .. tostring(item_name))
vlayer_data.storage.items[item_name] = vlayer_data.storage.items[item_name] + count
if not config.unlimited_surface_area and item_properties.required_area and item_properties.required_area > 0 then
-- Calculate how many can be allocated
local surplus_area = vlayer_data.properties.total_surface_area - vlayer_data.properties.used_surface_area
local allocate_count = math.min(count, math.floor(surplus_area / item_properties.required_area))
if allocate_count > 0 then
vlayer.allocate_item(item_name, allocate_count)
end
vlayer_data.storage.unallocated[item_name] = vlayer_data.storage.unallocated[item_name] + count - allocate_count
else
vlayer.allocate_item(item_name, count)
end
end
--- Remove an item from the vlayer, this will decrement its count in storage and prioritise unallocated items over deallocation
-- Can not always fulfil the remove request for items which provide surface area, therefore returns the amount actually removed
-- @tparam string item_name The name of the item to remove
-- @tparam number count The count of the item to remove
-- @treturn number The count of the item actually removed
function vlayer.remove_item(item_name, count)
local item_properties = config.allowed_items[item_name]
assert(item_properties, 'Item not allowed in vlayer: ' .. tostring(item_name))
local remove_unallocated = 0
if not config.unlimited_surface_area and item_properties.required_area and item_properties.required_area > 0 then
-- Remove from the unallocated storage first
remove_unallocated = math.min(count, vlayer_data.storage.unallocated[item_name])
if remove_unallocated > 0 then
vlayer_data.storage.items[item_name] = vlayer_data.storage.items[item_name] - count
vlayer_data.storage.unallocated[item_name] = vlayer_data.storage.unallocated[item_name] - count
end
-- Check if any more items need to be removed
count = count - remove_unallocated
if count == 0 then
return remove_unallocated
end
end
-- Calculate the amount to remove based on items in storage
local remove_count = math.min(count, vlayer_data.storage.items[item_name])
if item_properties.surface_area and item_properties.surface_area > 0 then
-- If the item provides surface area then it has additional limitations
local surplus_area = vlayer_data.properties.total_surface_area - vlayer_data.properties.used_surface_area
remove_count = math.min(remove_count, math.floor(surplus_area / item_properties.surface_area))
if remove_count <= 0 then
return remove_unallocated
end
end
-- Remove the item from allocated storage
vlayer_data.storage.items[item_name] = vlayer_data.storage.items[item_name] - remove_count
vlayer.allocate_item(item_name, -remove_count)
return remove_unallocated + remove_count
end
--- Create a new storage input interface
-- @tparam LuaSurface surface The surface to place the interface onto
-- @tparam MapPosition position The position on the surface to place the interface at
-- @tparam[opt] LuaPlayer player The player to show as the last user of the interface
-- @treturn LuaEntity The entity that was created for the interface
function vlayer.create_input_interface(surface, position, circuit, last_user)
local interface = surface.create_entity{name='logistic-chest-storage', position=position, force='neutral'}
table.insert(vlayer_data.entity_interfaces.storage_input, interface)
if last_user then
interface.last_user = last_user
end
if circuit then
for k, _ in pairs(circuit) do
for _, v in pairs(circuit[k]) do
interface.connect_neighbour({wire=defines.wire_type[k], target_entity=v})
end
end
end
interface.destructible = false
interface.minable = false
interface.operable = true
return interface
end
--- Handle all input interfaces, will take their contents and insert it into the vlayer storage
local function handle_input_interfaces()
for index, interface in pairs(vlayer_data.entity_interfaces.storage_input) do
if not interface.valid then
vlayer_data.entity_interfaces.storage_input[index] = nil
else
local inventory = interface.get_inventory(defines.inventory.chest)
for name, count in pairs(inventory.get_contents()) do
if config.allowed_items[name] then
if config.allowed_items[name].modded then
if config.modded_auto_downgrade then
vlayer.insert_item(config.modded_items[name].base_game_equivalent, count * config.modded_items[name].multiplier)
else
vlayer.insert_item(name, count)
end
else
if vlayer_data.storage.power_items[name] then
vlayer_data.storage.power_items[name].count = vlayer_data.storage.power_items[name].count + count
else
vlayer.insert_item(name, count)
end
end
inventory.remove({name=name, count=count})
end
end
end
end
end
--- Create a new storage output interface
-- @tparam LuaSurface surface The surface to place the interface onto
-- @tparam MapPosition position The position on the surface to place the interface at
-- @tparam[opt] LuaPlayer player The player to show as the last user of the interface
-- @treturn LuaEntity The entity that was created for the interface
function vlayer.create_output_interface(surface, position, circuit, last_user)
local interface = surface.create_entity{name='logistic-chest-requester', position=position, force='neutral'}
table.insert(vlayer_data.entity_interfaces.storage_output, interface)
if last_user then
interface.last_user = last_user
end
if circuit then
for k, _ in pairs(circuit) do
for _, v in pairs(circuit[k]) do
interface.connect_neighbour({wire=defines.wire_type[k], target_entity=v})
end
end
end
interface.destructible = false
interface.minable = false
interface.operable = true
return interface
end
--- Handle all output interfaces, will take their requests and remove it from the vlayer storage
local function handle_output_interfaces()
for index, interface in pairs(vlayer_data.entity_interfaces.storage_output) do
if not interface.valid then
vlayer_data.entity_interfaces.storage_output[index] = nil
else
local inventory = interface.get_inventory(defines.inventory.chest)
for i = 1, interface.request_slot_count do
local request = interface.get_request_slot(i)
if request and config.allowed_items[request.name] then
local current_amount = inventory.get_item_count(request.name)
local request_amount = math.min(request.count - current_amount, vlayer_data.storage.items[request.name])
if request_amount > 0 and inventory.can_insert({name=request.name, count=request_amount}) then
local removed_item_count = vlayer.remove_item(request.name, request_amount)
if removed_item_count > 0 then
inventory.insert({name=request.name, count=removed_item_count})
end
end
end
end
end
end
end
--- Handle the unallocated items because more surface area may have been added
local function handle_unallocated()
-- unallocated cant happen when its unlimited
if config.unlimited_surface_area then
return
end
-- Get the total unallocated area so items can be allocated in equal amounts
local unallocated_area = 0
for item_name, count in pairs(vlayer_data.storage.unallocated) do
local item_properties = config.allowed_items[item_name]
unallocated_area = unallocated_area + item_properties.required_area * count
end
if unallocated_area == 0 then
return
end
-- Allocate items in an equal distribution
local surplus_area = vlayer_data.properties.total_surface_area - vlayer_data.properties.used_surface_area
for item_name, count in pairs(vlayer_data.storage.unallocated) do
local allocation_count = math.min(count, math.floor(count * surplus_area / unallocated_area))
if allocation_count > 0 then
vlayer_data.storage.unallocated[item_name] = vlayer_data.storage.unallocated[item_name] - allocation_count
vlayer.allocate_item(item_name, allocation_count)
end
end
end
--- Get the statistics for the vlayer
function vlayer.get_statistics()
local vdp = vlayer_data.properties.production * mega
local gdm = get_production_multiplier()
return {
total_surface_area = vlayer_data.properties.total_surface_area,
used_surface_area = vlayer_data.properties.used_surface_area,
remaining_surface_area = get_actual_land_defecit(),
production_multiplier = gdm,
energy_max = vdp,
energy_production = vdp * gdm,
energy_sustained = vdp * get_sustained_multiplier(),
energy_capacity = vlayer_data.properties.capacity * mega,
energy_storage = vlayer_data.storage.energy,
day_time = math.floor(vlayer_data.surface.daytime * vlayer_data.surface.ticks_per_day),
day_length = vlayer_data.surface.ticks_per_day,
tick = game.tick
}
end
--- add or reduce vlayer power
function vlayer.energy_changed(power)
vlayer_data.storage.energy = vlayer_data.storage.energy + power
end
--- Circuit signals used for the statistics
function vlayer.get_circuits()
return {
total_surface_area = 'signal-A',
used_surface_area = 'signal-U',
remaining_surface_area = 'signal-R',
production_multiplier = 'signal-M',
energy_production = 'signal-P',
energy_sustained = 'signal-S',
energy_capacity = 'signal-C',
energy_storage = 'signal-E',
day_time = 'signal-D',
day_length = 'signal-L',
tick = 'signal-T',
}
end
--- Create a new circuit interface
-- @tparam LuaSurface surface The surface to place the interface onto
-- @tparam MapPosition position The position on the surface to place the interface at
-- @tparam[opt] LuaPlayer player The player to show as the last user of the interface
-- @treturn LuaEntity The entity that was created for the interface
function vlayer.create_circuit_interface(surface, position, circuit, last_user)
local interface = surface.create_entity{name='constant-combinator', position=position, force='neutral'}
table.insert(vlayer_data.entity_interfaces.circuit, interface)
if last_user then
interface.last_user = last_user
end
if circuit then
for k, _ in pairs(circuit) do
for _, v in pairs(circuit[k]) do
interface.connect_neighbour({wire=defines.wire_type[k], target_entity=v})
end
end
end
interface.destructible = false
interface.minable = false
interface.operable = true
return interface
end
--- Handle all circuit interfaces, updating their signals to match the vlayer statistics
local function handle_circuit_interfaces()
local stats = vlayer.get_statistics()
for index, interface in pairs(vlayer_data.entity_interfaces.circuit) do
if not interface.valid then
vlayer_data.entity_interfaces.circuit[index] = nil
else
local circuit_oc = interface.get_or_create_control_behavior()
local max_signals = circuit_oc.signals_count
local signal_index = 1
local circuit = vlayer.get_circuits()
-- Set the virtual signals based on the vlayer stats
for stat_name, signal_name in pairs(circuit) do
if stat_name:find('energy') then
circuit_oc.set_signal(signal_index, {signal={type='virtual', name=signal_name}, count=math.floor(stats[stat_name] / mega)})
elseif stat_name == 'production_multiplier' then
circuit_oc.set_signal(signal_index, {signal={type='virtual', name=signal_name}, count=math.floor(stats[stat_name] * 10000)})
else
circuit_oc.set_signal(signal_index, {signal={type='virtual', name=signal_name}, count=math.floor(stats[stat_name])})
end
signal_index = signal_index + 1
end
-- Set the item signals based on stored items
for item_name, count in pairs(vlayer_data.storage.items) do
if game.item_prototypes[item_name] and count > 0 then
circuit_oc.set_signal(signal_index, {signal={type='item', name=item_name}, count=count})
signal_index = signal_index + 1
if signal_index > max_signals then
return -- No more signals can be added
end
end
end
-- Clear remaining signals to prevent outdated values being present (caused by count > 0 check)
for clear_index = signal_index, max_signals do
if not circuit_oc.get_signal(clear_index).signal then
break -- There are no more signals to clear
end
circuit_oc.set_signal(clear_index, nil)
end
end
end
end
--- Create a new energy interface
-- @tparam LuaSurface surface The surface to place the interface onto
-- @tparam MapPosition position The position on the surface to place the interface at
-- @tparam[opt] LuaPlayer player The player to show as the last user of the interface
-- @treturn LuaEntity The entity that was created for the interface, or nil if it could not be created
function vlayer.create_energy_interface(surface, position, last_user)
if not surface.can_place_entity{name='electric-energy-interface', position=position} then
return nil
end
local interface = surface.create_entity{name='electric-energy-interface', position=position, force='neutral'}
table.insert(vlayer_data.entity_interfaces.energy, interface)
if last_user then
interface.last_user = last_user
end
interface.destructible = false
interface.minable = false
interface.operable = false
interface.electric_buffer_size = 0
interface.power_production = 0
interface.power_usage = 0
interface.energy = 0
return interface
end
--- Handle all energy interfaces as well as the energy storage
local function handle_energy_interfaces()
-- Add the newly produced power
local production = vlayer_data.properties.production * mega * (config.update_tick_energy / 60)
vlayer_data.storage.energy = vlayer_data.storage.energy + math.floor(production * get_production_multiplier())
-- Calculate how much power is present in the network, that is storage + all interfaces
if #vlayer_data.entity_interfaces.energy > 0 then
local available_energy = vlayer_data.storage.energy
for index, interface in pairs(vlayer_data.entity_interfaces.energy) do
if not interface.valid then
vlayer_data.entity_interfaces.energy[index] = nil
else
available_energy = available_energy + interface.energy
end
end
-- Distribute the energy between all interfaces
local discharge_rate = 2 * (production + vlayer_data.properties.discharge * mega) / #vlayer_data.entity_interfaces.energy
local fill_to = math.min(discharge_rate, math.floor(available_energy / #vlayer_data.entity_interfaces.energy))
for _, interface in pairs(vlayer_data.entity_interfaces.energy) do
interface.electric_buffer_size = math.max(discharge_rate, interface.energy) -- prevent energy loss
local delta = fill_to - interface.energy -- positive means storage to interface
vlayer_data.storage.energy = vlayer_data.storage.energy - delta
interface.energy = interface.energy + delta
end
end
-- Cap the stored energy to the allowed capacity
if not config.unlimited_capacity and vlayer_data.storage.energy > vlayer_data.properties.capacity * mega then
vlayer_data.storage.energy = vlayer_data.properties.capacity * mega
-- burn the trash to produce power
elseif vlayer_data.storage.power_items then
for k, v in pairs(vlayer_data.storage.power_items) do
local max_burning = (vlayer_data.properties.capacity * mega / 2) - vlayer_data.storage.energy
if v.count > 0 and max_burning > 0 then
local to_burn = math.min(v.count, math.floor(max_burning / v.value))
vlayer_data.storage.energy = vlayer_data.storage.energy + (to_burn * v.value)
vlayer_data.storage.power_items[k].count = vlayer_data.storage.power_items[k].count - to_burn
end
end
end
end
--- Remove the entity interface using the given position
-- @tparam LuaSurface surface The surface to search for an interface on
-- @tparam MapPosition position The position of the item
-- @treturn string The type of interface that was removed, or nil if no interface was found
-- @treturn MapPosition The position the interface was at, or nil if no interface was found
function vlayer.remove_interface(surface, position)
local entities = surface.find_entities_filtered{
name = {'logistic-chest-storage', 'logistic-chest-requester', 'constant-combinator', 'electric-energy-interface'},
force = 'neutral',
position = position,
radius = 2,
limit = 1
}
-- Get the details which will be returned
if #entities == 0 then
return nil, nil
end
local interface = entities[1]
local name = interface.name
local pos = interface.position
-- Return the type of interface removed and do some clean up
if name == 'logistic-chest-storage' then
move_items_stack(interface.get_inventory(defines.inventory.chest).get_contents())
table.remove_element(vlayer_data.entity_interfaces.storage_input, interface)
interface.destroy()
return 'storage-input', pos
elseif name == 'logistic-chest-requester' then
move_items_stack(interface.get_inventory(defines.inventory.chest).get_contents())
table.remove_element(vlayer_data.entity_interfaces.storage_output, interface)
interface.destroy()
return 'storage-output', pos
elseif name == 'constant-combinator' then
table.remove_element(vlayer_data.entity_interfaces.circuit, interface)
interface.destroy()
return 'circuit', pos
elseif name == 'electric-energy-interface' then
vlayer_data.storage.energy = vlayer_data.storage.energy + interface.energy
table.remove_element(vlayer_data.entity_interfaces.energy, interface)
interface.destroy()
return 'energy', pos
end
end
local function on_surface_event()
if config.mimic_surface then
local surface = game.get_surface(config.mimic_surface)
if surface then
vlayer_data.surface = surface
return
end
end
if not vlayer_data.surface.index then
-- Our fake surface data never has an index, we test for this to avoid unneeded copies from the config
vlayer_data.surface = table.deep_copy(config.surface)
end
end
--- Handle all storage IO and attempt allocation of unallocated items
Event.on_nth_tick(config.update_tick_storage, function(_)
handle_input_interfaces()
handle_output_interfaces()
handle_unallocated()
end)
--- Handle all energy and circuit updates
Event.on_nth_tick(config.update_tick_energy, function(_)
handle_circuit_interfaces()
handle_energy_interfaces()
end)
Event.add(defines.events.on_surface_created, on_surface_event)
Event.add(defines.events.on_surface_renamed, on_surface_event)
Event.add(defines.events.on_surface_imported, on_surface_event)
Event.on_init(on_surface_event) -- Default surface always exists, does not trigger on_surface_created
return vlayer

View File

@@ -0,0 +1,331 @@
--[[-- 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.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 'utils.event' --- @dep utils.event
local Game = require 'utils.game' --- @dep utils.game
local Global = require 'utils.global' --- @dep utils.global
local config = require 'config.warnings' --- @dep config.warnings
local valid_player = Game.get_player_from_any
--- Stores the quickbar filters for a player
local PlayerData = require '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
Global.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
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

View File

@@ -0,0 +1,483 @@
--[[-- Control Module - Warps
- Stores warps for each force.
@control Warps
@alias Warps
@usage-- Making a new spawn warp
local player = game.player
local force = player.force
local spawn_id = Warps.add_warp(force.name, player.surface, player.position, player.name, 'Spawn')
Warps.set_spawn_warp(spawn_id, force)
Warps.make_warp_tag(spawn_id)
@usage-- Making a new warp with a warp area
local player = game.player
local force = player.force
local warp_id = Warps.add_warp(force.name, player.surface, player.position, player.name)
Warps.make_warp_area(warp_id)
Warps.make_warp_tag(warp_id)
]]
local Datastore = require 'expcore.datastore' --- @dep expcore.datastore
local Global = require 'utils.global' --- @dep utils.global
local config = require 'config.gui.warps' --- @dep config.warps
--- Stores all data for the warp system
local WrapData = Datastore.connect('WrapData')
WrapData:set_serializer(function(raw_key) return raw_key.warp_id end)
local Warps = {}
-- Global lookup table for force name to task ids
local force_warps = {_uid=1}
---@cast force_warps table<string, { spawn: string, [number]: string }>
Global.register(force_warps, function(tbl)
force_warps = tbl
end)
-- Create an array of entity names that will be added to the remove filter
local remove_warp_area_entity_names = {}
for _, entity in pairs(config.entities) do
table.insert(remove_warp_area_entity_names, entity[1])
end
-- When a warp is updated change its chat tag and restore the warp order
WrapData:on_update(function(warp_id, warp, old_warp)
if warp then
warp.updates = warp.updates + 1
-- Update the map chart tag if there is one
if warp.tag then
Warps.make_warp_tag(warp_id)
end
-- Check that the name of the warp has been changed
if not old_warp or warp.name == old_warp.name then return end
-- Get the names of all the warp points for this force
local force_name = warp.force_name
local warp_ids = force_warps[force_name]
local spawn_id = warp_ids.spawn
local warp_names = {}
for _, next_warp_id in pairs(warp_ids) do
local next_warp = WrapData:get(next_warp_id)
if next_warp_id ~= spawn_id then
warp_names[next_warp.name..next_warp_id] = next_warp_id
end
end
-- Sort the warp names in alphabetical order
local new_warp_ids = table.get_values(table.keysort(warp_names))
table.insert(new_warp_ids, 1,spawn_id)
new_warp_ids.spawn = spawn_id
force_warps[force_name] = new_warp_ids
end
end)
--- Map Integration.
-- functions used to create and alter warps with in the map
-- @section mapIntegration
--[[-- Add or update the chat tag for this warp
@tparam string warp_id the uid of the warp you want the chart tag for
@treturn boolean true if a new tag was made, false if it was updated
@usage-- Adding a chart tag for a new warp
local tag_added = Warps.make_warp_tag(warp_id)
]]
function Warps.make_warp_tag(warp_id)
local warp = WrapData:get(warp_id)
local name = warp.name
local icon = warp.icon
-- Edit the existing tag if it is present
local tag = warp.tag
if tag and tag.valid then
tag.text = 'Warp: ' .. name
tag.icon = icon
return false
end
-- Make a new tag if one did not exist
local force = game.forces[warp.force_name]
local surface = warp.surface
local position = warp.position
tag = force.add_chart_tag(surface, {
position = {position.x+0.5, position.y+0.5},
text = 'Warp: ' .. name,
icon = icon
})
-- Add the tag to this warp, store.update not needed as we dont want it to trigger
warp.tag = tag
return true
end
--[[-- Remove the chart tag for this warp
@tparam string warp_id the uid for the warp that you want to remove the chart tag from
@treturn boolean true if the tag was valid and was removed, false if the tag was invalid
@usage-- Removing the chart tag from a warp
local removed = Warps.remove_warp_tag(warp_id)
]]
function Warps.remove_warp_tag(warp_id)
local warp = WrapData:get(warp_id)
-- Check there is a tag to remove
local tag = warp.tag
if not tag or not tag.valid then
warp.tag = nil
return false
end
-- Remove the warp chart tag if it is valid
tag.destroy()
warp.tag = nil
return true
end
--[[-- Add a warp area for the warp, purely cosmetic
@tparam string warp_id the uid of the warp you want the area for
@usage-- Adding a warp area for a warp
Warps.make_warp_area(warp_id)
]]
function Warps.make_warp_area(warp_id)
local warp = WrapData:get(warp_id)
local surface = warp.surface
local position = warp.position
local posx = position.x
local posy = position.y
-- Get the tile that is being replaced, store.update not needed as we don't want it to trigger
local old_tile = surface.get_tile(position).name
warp.old_tile = old_tile
-- Add a tile pattern on top of the base
local tiles = {}
for _, tile in pairs(config.tiles) do
table.insert(tiles, {name=tile[1], position={tile[2]+posx, tile[3]+posy}})
end
surface.set_tiles(tiles)
-- Add entities to the warp structure
for _, entity in pairs(config.entities) do
entity = surface.create_entity{
name=entity[1],
position={entity[2]+posx, entity[3]+posy},
force='neutral'
}
entity.destructible = false
entity.health = 0
entity.minable = false
entity.rotatable = false
-- Save reference of the last power pole
if entity.type == 'electric-pole' then
warp.electric_pole = entity
end
end
end
--[[-- Remove the warp area for a warp
@tparam string warp_id the uid of the warp that you want to remove the area for
@usage-- Remove the warp area for a warp
Warps.remove_warp_area(warp_id)
]]
function Warps.remove_warp_area(warp_id)
local warp = WrapData:get(warp_id)
local position = warp.position
local surface = warp.surface
local radius = config.standard_proximity_radius
-- Check that a warp area was created previously
local old_tile = warp.old_tile
if not old_tile then return end
-- Restore the original tiles before the creation of the warp
local tiles = {}
for _, tile in pairs(config.tiles) do
table.insert(tiles, {name=old_tile, position={tile[2]+position.x, tile[3]+position.y}})
end
surface.set_tiles(tiles)
local area = {
{position.x-radius, position.y-radius},
{position.x+radius, position.y+radius}
}
-- Remove warp structure entities
local entities = surface.find_entities_filtered{ force='neutral', area=area, name = remove_warp_area_entity_names }
for _, entity in pairs(entities) do
-- Destroy them, this will leave corpses of the entities that it destroyed.
if entity and entity.valid and entity.destructible == false then
entity.destructible = true
entity.die(entity.force)
end
end
-- Rechart map area, useful if warp is not covered by a radar
game.forces[warp.force_name].chart(surface, area)
end
--[[-- Set a warp to be the spawn point for a force, force must own this warp
@tparam string warp_id the uid of the warp that you want to be the spawn for the force
@tparam LuaForce force the force that you want to set the spawn for
@usage-- Set your forces spawn to a warp
Warps.set_spawn_warp(warp_id, game.player.force)
]]
function Warps.set_spawn_warp(warp_id, force)
-- Check the force owns this warp
local warp = WrapData:get(warp_id)
if warp.force_name ~= force.name then
return
end
-- Set this warp as the spawn
local warp_ids = force_warps[warp.force_name]
if not warp_ids then
warp_ids = {}
force_warps[warp.force_name] = warp_ids
end
warp_ids.spawn = warp_id
-- Set the forces spawn to this warp
force.set_spawn_position(warp.position, warp.surface)
end
--[[-- Teleport a player to a warp point
@tparam string warp_id the uid of the warp to send the player to
@tparam LuaPlayer player the player to teleport to the warp
@usage-- Teleport yourself to a warp point
Warps.teleport_player(warp_id, game.player)
]]
function Warps.teleport_player(warp_id, player)
local warp = WrapData:get(warp_id)
local surface = warp.surface
local position = {
x=warp.position.x+0.5,
y=warp.position.y+0.5
}
if player.vehicle then
-- Teleport the entity
local entity = player.vehicle
local goto_position = surface.find_non_colliding_position(entity.name, position, 32, 1)
-- Surface teleport can only be done for players and cars at the moment. (with surface as an peramitor it gives this error)
if entity.type == "car" then
entity.teleport(goto_position, surface)
elseif surface.index == entity.surface.index then
-- Try teleport the entity
if not entity.teleport(goto_position) then
player.driving = false
-- Need to calculate new goto_position because entities have different collision boxes
goto_position = surface.find_non_colliding_position('character', position, 32, 1)
player.teleport(goto_position, surface)
end
end
else
-- Teleport the player
local goto_position = surface.find_non_colliding_position('character', position, 32, 1)
if player.driving then player.driving = false end
player.teleport(goto_position, surface)
end
end
--- Setters.
-- functions used to created and alter warps
-- @section setters
--[[-- Add a new warp for a force, the warp must have a surface and a position
@tparam string force_name the name of the force to add the warp for
@tparam LuaSurface surface the surface that the warp will be on
@tparam Concepts.Position position the position that the warp will be on
@tparam[opt] string player_name the name of the player that is adding the warp
@tparam[opt] string warp_name the name of the warp that is being added, if omited default is used
@treturn string the uid of the warp which was created
@usage-- Adding a new warp for your force at your position
local player = game.player
local warp_id = Warps.add_warp(player.force.name, player.surface, player.position, player.name)
]]
function Warps.add_warp(force_name, surface, position, player_name, warp_name)
-- Get new warp id
local warp_id = tostring(force_warps._uid)
force_warps._uid = force_warps._uid + 1
warp_name = warp_name or 'New warp'
-- Get the existing warps for this force
local warp_ids = force_warps[force_name]
if not warp_ids then
warp_ids = {}
force_warps[force_name] = warp_ids
end
-- Insert the warp id into the force warps
table.insert(warp_ids, warp_id)
-- Create the editing table
local editing = {}
if player_name then
editing[player_name] = true
end
-- Add the new warp to the store
WrapData:set(warp_id, {
warp_id = warp_id,
force_name = force_name,
name = warp_name,
icon = { type = config.default_icon.type, name = config.default_icon.name },
surface = surface,
position = {
x = math.floor(position.x),
y = math.floor(position.y)
},
last_edit_name = player_name or '<server>',
last_edit_time = game.tick,
currently_editing = editing,
updates = 0,
})
return warp_id
end
--[[-- Removes a warp and any data linked to it
@tparam string warp_id the uid of the warp that you want to remove
@usage-- Removing a warp
Warps.remove_warp(warp_id)
]]
function Warps.remove_warp(warp_id)
local warp = WrapData:get(warp_id)
local force_name = warp.force_name
Warps.remove_warp_tag(warp_id)
Warps.remove_warp_area(warp_id)
WrapData:remove(warp_id)
table.remove_element(force_warps[force_name], warp_id)
end
--[[-- Update the name and icon for a warp
@tparam string warp_id the uid of the warp that you want to update
@tparam[opt] string new_name the new name that you want the warp to have
@tparam[opt] string new_icon the new icon that you want the warp to have
@tparam[opt='server'] string player_name the name of the player that made the edit
@usage-- Changing the name and icon for a warp
Warps.update_warp(warp_id, 'My Warp', 'iron-plate', game.player.name)
]]
function Warps.update_warp(warp_id, new_name, new_icon, player_name)
WrapData:update(warp_id, function(_, warp)
-- If the icon is not valid then replace with the old icon
if new_icon and not new_icon.name or not new_icon.type then
new_icon = warp.icon
end
warp.last_edit_name = player_name or '<server>'
warp.last_edit_time = game.tick
warp.name = new_name or warp.name
warp.icon = new_icon or warp.icon
end)
end
--[[-- Set the editing state for a player, can be used as a warning or to display a text field
@tparam string warp_id the uid of the warp that you want to effect
@tparam string player_name the name of the player you want to set the state for
@tparam boolean state the new state to set editing to
@usage-- Setting your editing state to true
Warps.set_editing(warp_id, game.player.name, true)
]]
function Warps.set_editing(warp_id, player_name, state)
WrapData:update(warp_id, function(_, warp)
warp.currently_editing[player_name] = state
end)
end
--[[-- Adds an update handler for when a warp is added, removed, or updated
@tparam function handler the handler which is called when a warp is updated
@usage-- Add a game print when a warp is updated
Warps.on_update(function(warp)
game.print(warp.force_name..' now has the warp: '..warp.name)
end)
]]
function Warps.on_update(handler)
WrapData:on_update(handler)
end
--- Getters.
-- function used to get information about warps
-- @section getters
--[[-- Gets the warp information that is linked with this id
@tparam string warp_id the uid of the warp you want to get
@treturn table the warp information
@usage-- Getting warp information outside of on_update
local warp = Warps.get_warp(warp_id)
]]
function Warps.get_warp(warp_id)
return WrapData:get(warp_id)
end
--[[-- Gets all the warp ids that a force has
@tparam string force_name the name of the force that you want the warp ids for
@treturn table an array of all the warp ids
@usage-- Getting the warp ids for a force
local warp_ids = Warps.get_force_warp_ids(game.player.force.name)
]]
function Warps.get_force_warp_ids(force_name)
return force_warps[force_name] or {}
end
--[[-- Get the id of the spawn warp
@tparam string force_name the name of the force that you want to get the spawn warp for
@treturn ?string|nil the uid of the spawn warp for this force if there is one
@usage-- Getting the spawn warp id
local spawn_id = Warps.get_spawn_warp_id(game.player.force.name)
]]
function Warps.get_spawn_warp_id(force_name)
local warp_ids = force_warps[force_name] or {}
return warp_ids.spawn
end
--[[-- Gets the editing state for a player
@tparam string warp_id the uid of the warp you want to check
@tparam string player_name the name of the player that you want to check
@treturn boolean weather the player is currently editing this warp
@usage-- Check if a player is editing a warp or not
local editing = Warps.get_editing(warp_id, game.player.name)
]]
function Warps.get_editing(warp_id, player_name)
local warp = WrapData:get(warp_id)
return warp.currently_editing[player_name]
end
-- Module return
return Warps

View File

@@ -0,0 +1,24 @@
--- Stores if you use alt mode or not and auto applies it
-- @data Alt-View
local Event = require 'utils.event' ---@dep utils.event
--- Stores the visible state of alt mode
local PlayerData = require 'expcore.player_data' --- @dep expcore.player_data
local UsesAlt = PlayerData.Settings:combine('UsesAlt')
UsesAlt:set_default(false)
UsesAlt:set_metadata{
stringify = function(value) return value and 'Visible' or 'Hidden' end
}
--- When your data loads apply alt view if you have it enabled
UsesAlt:on_load(function(player_name, uses_alt)
local player = game.players[player_name]
player.game_view_settings.show_entity_info = uses_alt or false
end)
--- When alt view is toggled update this
Event.add(defines.events.on_player_toggled_alt_mode, function(event)
local player = game.players[event.player_index]
UsesAlt:set(player, player.game_view_settings.show_entity_info)
end)

View File

@@ -0,0 +1,103 @@
--[[-- Commands Module - Bonus
- Adds a command that allows players to have increased stats
@data Bonus
]]
local Roles = require 'expcore.roles' --- @dep expcore.roles
local Event = require 'utils.event' --- @dep utils.event
local config = require 'config.bonus' --- @dep config.bonuses
local Commands = require 'expcore.commands' --- @dep expcore.commands
require 'config.expcore.command_general_parse'
-- Stores the bonus for the player
local PlayerData = require 'expcore.player_data' --- @dep expcore.player_data
local PlayerBonus = PlayerData.Settings:combine('Bonus')
PlayerBonus:set_default(0)
PlayerBonus:set_metadata{
permission = 'command/bonus',
stringify = function(value)
if not value or value == 0 then
return 'None set'
end
return value
end
}
--- Apply a bonus to a player
local function apply_bonus(player, stage)
if not player.character then
return
end
for k, v in pairs(config.player_bonus) do
player[k] = v.value * stage / 10
if v.combined_bonus then
for i=1, #v.combined_bonus, 1 do
player[v.combined_bonus[i]] = v.value * stage / 10
end
end
end
end
--- When store is updated apply new bonus to the player
PlayerBonus:on_update(function(player_name, player_bonus)
apply_bonus(game.players[player_name], player_bonus or 0)
end)
--- Changes the amount of bonus you receive
-- @command bonus
-- @tparam number amount range 0-10 the increase for your bonus
Commands.new_command('bonus', {'expcom-bonus.description'}, 'Changes the amount of bonus you receive')
:add_param('amount', 'integer-range', 0, 10)
:register(function(player, amount)
if not Roles.player_allowed(player, 'command/bonus') then
Commands.print{'expcom-bonus.perm', 1}
return
end
PlayerBonus:set(player, amount)
Commands.print{'expcom-bonus.set', amount}
end)
--- When a player respawns re-apply bonus
Event.add(defines.events.on_player_respawned, function(event)
local player = game.players[event.player_index]
apply_bonus(player, PlayerBonus:get(player))
end)
--- Remove bonus if a player no longer has access to the command
local function role_update(event)
local player = game.players[event.player_index]
if not Roles.player_allowed(player, 'command/bonus') then
apply_bonus(player, 0)
end
end
--- When a player dies allow them to have instant respawn
Event.add(defines.events.on_player_died, function(event)
local player = game.players[event.player_index]
if Roles.player_has_flag(player, 'instant-respawn') then
player.ticks_to_respawn = 120
end
end)
Event.add(defines.events.on_player_created, function(event)
if event.player_index ~= 1 then
return
end
for k, v in pairs(config.force_bonus) do
game.players[event.player_index].force[k] = v.value
end
for k, v in pairs(config.surface_bonus) do
game.players[event.player_index].surface[k] = v.value
end
end)
Event.add(Roles.events.on_role_assigned, role_update)
Event.add(Roles.events.on_role_unassigned, role_update)

View File

@@ -0,0 +1,43 @@
--- Greets players on join
-- @data Greetings
local config = require 'config.join_messages' --- @dep config.join_messages
local Commands = require 'expcore.commands' ---@dep expcore.commands
require 'config.expcore.command_general_parse'
--- Stores the join message that the player have
local PlayerData = require 'expcore.player_data' --- @dep expcore.player_data
local CustomMessages = PlayerData.Settings:combine('JoinMessage')
CustomMessages:set_metadata{
permission = 'command/join-message'
}
--- When a players data loads show their message
CustomMessages:on_load(function(player_name, player_message)
local player = game.players[player_name]
local custom_message = player_message or config[player_name]
if custom_message then
game.print(custom_message, player.color)
else
player.print{'join-message.greet', {'links.discord'}}
end
end)
--- Set your custom join message
-- @command join-message
-- @tparam string message The custom join message that will be used
Commands.new_command('join-message', {'expcom-join-message.description-msg'}, 'Sets your custom join message')
:add_param('message', false, 'string-max-length', 255)
:enable_auto_concat()
:register(function(player, message)
if not player then return end
CustomMessages:set(player, message)
return {'join-message.message-set'}
end)
Commands.new_command('join-message-clear', {'expcom-join-message.description-clr'}, 'Clear your join message')
:register(function(player)
if not player then return end
CustomMessages:remove(player)
return {'join-message.message-cleared'}
end)

View File

@@ -0,0 +1,34 @@
--- Stores the language used to join the server
-- @data Language
local Event = require 'utils.event' ---@dep utils.event
local PlayerData = require 'expcore.player_data' --- @dep expcore.player_data
local LocalLanguage = PlayerData.Statistics:combine('LocalLanguage')
LocalLanguage:set_default("Unknown")
--- Creates translation request on_load of a player
LocalLanguage:on_load(function(player_name, _)
local player = game.players[player_name]
player.request_translation({"language.local-language"})
end)
--- Resolves translation request for language setting
Event.add(defines.events.on_string_translated, function(event)
-- Check if event.localised_string is a table
-- Check if the translation request was for language setting
if type(event.localised_string) ~= "table" or event.localised_string[1] ~= "language.local-language" then
return
end
-- Check if the translation request was succesful
local player = game.players[event.player_index]
if not event.translated then
player.print("Could not detect your language settings")
-- Raise error
return
end
-- Change LocalLanguage value for the player to the recognized one
local language = event.result
LocalLanguage:set(player, language)
end)

View File

@@ -0,0 +1,97 @@
local Commands = require 'expcore.commands' --- @dep expcore.commands
local config = require 'config.personal_logistic' --- @dep config.personal-logistic
local pl = {}
function pl.pl(type, target, amount)
local c
local s
if type == 'p' then
c = target.clear_personal_logistic_slot
s = target.set_personal_logistic_slot
elseif type == 's' then
c = target.clear_vehicle_logistic_slot
s = target.set_vehicle_logistic_slot
else
return
end
for _, v in pairs(config.request) do
c(v.key)
end
if (amount < 0) then
return
end
local stats = target.force.item_production_statistics
for k, v in pairs(config.request) do
local v_min = math.ceil(v.min * amount)
local v_max = math.ceil(v.max * amount)
if v.stack and v.stack ~= 1 and v.type ~= 'weapon' then
v_min = math.floor(v_min / v.stack) * v.stack
v_max = math.ceil(v_max / v.stack) * v.stack
end
if v.upgrade_of == nil then
if v.type then
if stats.get_input_count(k) < config.production_required[v.type] then
if v_min > 0 then
if v_min == v_max then
v_min = math.floor((v_max * 0.5) / v.stack) * v.stack
end
else
v_min = 0
end
end
end
s(v.key, {name=k, min=v_min, max=v_max})
else
if v.type then
if stats.get_input_count(k) >= config.production_required[v.type] then
s(v.key, {name=k, min=v_min, max=v_max})
local vuo = v.upgrade_of
while vuo do
s(config.request[vuo].key, {name=vuo, min=0, max=0})
vuo = config.request[vuo].upgrade_of
end
else
s(v.key, {name=k, min=0, max=v_max})
end
end
end
end
end
Commands.new_command('personal-logistic', {'expcom-personal-logistics'}, 'Set Personal Logistic (-1 to cancel all) (Select spidertron to edit spidertron)')
:add_param('amount', 'integer-range', -1, 10)
:add_alias('pl')
:register(function(player, amount)
if player.force.technologies['logistic-robotics'].researched then
if player.selected then
if player.selected.name == 'spidertron' then
pl.pl('s', player.selected, amount / 10)
return Commands.success
end
else
pl.pl('p', player, amount / 10)
return Commands.success
end
else
player.print('Personal Logistic not researched')
end
end)
return pl

View File

@@ -0,0 +1,62 @@
--- Gives players random colours when they join, also applies preset colours to those who have them
-- @data Player-Colours
local Event = require 'utils.event' --- @dep utils.event
local Colours = require 'utils.color_presets' --- @dep utils.color_presets
local config = require 'config.preset_player_colours' --- @dep config.preset_player_colours
--- Stores the colour that the player wants
local PlayerData = require 'expcore.player_data' --- @dep expcore.player_data
local PlayerColours = PlayerData.Settings:combine('Colour')
PlayerColours:set_metadata{
stringify = function(value)
if not value then return 'None set' end
local c = value[1]
return string.format('Red: %d Green: %d Blue: %d', c[1], c[2], c[3])
end
}
--- Used to compact player colours to take less space
local floor = math.floor
local function compact(colour)
return {
floor(colour.r * 255),
floor(colour.g * 255),
floor(colour.b * 255)
}
end
--- Returns a colour that is a bit lighter than the one given
local function lighten(c)
return {r = 255 - (255 - c.r) * 0.5, g = 255 - (255 - c.g) * 0.5, b = 255 - (255 - c.b) * 0.5, a = 255}
end
--- When your data loads apply the players colour, or a random on if none is saved
PlayerColours:on_load(function(player_name, player_colour)
if not player_colour then
local preset = config.players[player_name]
if preset then
player_colour = {preset, lighten(preset)}
else
local colour_name = 'white'
while config.disallow[colour_name] do
colour_name = table.get_random_dictionary_entry(Colours, true)
end
player_colour = {Colours[colour_name], lighten(Colours[colour_name])}
end
end
local player = game.players[player_name]
player.color = player_colour[1]
player.chat_color = player_colour[2]
end)
--- Save the players color when they use the color command
Event.add(defines.events.on_console_command, function(event)
if event.command ~= 'color' then return end
if event.parameters == '' then return end
if not event.player_index then return end
local player = game.players[event.player_index]
if not player or not player.valid then return end
PlayerColours:set(player, {compact(player.color), compact(player.chat_color)})
end)

View File

@@ -0,0 +1,67 @@
--[[-- Commands Module - Quickbar
- Adds a command that allows players to load Quickbar presets
@data Quickbar
]]
local Commands = require 'expcore.commands' --- @dep expcore.commands
local config = require 'config.preset_player_quickbar' --- @dep config.preset_player_quickbar
--- Stores the quickbar filters for a player
local PlayerData = require 'expcore.player_data' --- @dep expcore.player_data
local PlayerFilters = PlayerData.Settings:combine('QuickbarFilters')
PlayerFilters:set_metadata{
permission = 'command/save-quickbar',
stringify = function(value)
if not value then return 'No filters set' end
local count = 0
for _ in pairs(value) do count = count + 1 end
return count..' filters set'
end
}
--- 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
if item_name ~= nil and item_name ~= '' then
player.set_quick_bar_slot(i, item_name)
end
end
end)
local ignoredItems = {
["blueprint"] = true,
["blueprint-book"] = true,
["deconstruction-planner"] = true,
["spidertron-remote"] = true,
["upgrade-planner"] = true
}
--- Saves your quickbar preset to the script-output folder
-- @command save-quickbar
Commands.new_command('save-quickbar', {'expcom-quickbar.description'}, 'Saves your quickbar preset items to file')
:add_alias('save-toolbar')
:register(function(player)
local filters = {}
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 = ignoredItems[slot.name]
if ignored ~= true then
filters[i] = slot.name
end
end
end
if next(filters) then
PlayerFilters:set(player, filters)
else
PlayerFilters:remove(player)
end
return {'quickbar.saved'}
end)

View File

@@ -0,0 +1,189 @@
local Event = require 'utils.event' ---@dep utils.event
local Global = require 'utils.global' ---@dep utils.global
local config = require 'config.statistics' ---@dep config.statistics
local format_time = _C.format_time
local floor = math.floor
local afk_required = 5*3600 -- 5 minutes
--- Stores players who have been created, required to avoid loss of data
local new_players = {}
Global.register(new_players, function(tbl)
new_players = tbl
end)
--- Stores the statistics on a player
local PlayerData = require 'expcore.player_data' --- @dep expcore.player_data
local AllPlayerData = PlayerData.All
local Statistics = PlayerData.Statistics
Statistics:set_metadata{
display_order = config.display_order
}
--- Update your statistics with any which happened before the data was valid
Statistics:on_load(function(player_name, player_statistics)
local existing_data = AllPlayerData:get(player_name)
if existing_data and existing_data.valid then return end
local counters = config.counters
-- Merge all data from before you data loaded
for key, value in pairs(Statistics:get(player_name, {})) do
if config[key] or counters[key] then
if not player_statistics[key] then
player_statistics[key] = value
else
player_statistics[key] = player_statistics[key] + value
end
end
end
-- Increment your maps played if this is your first time on this map
if new_players[player_name] then
new_players[player_name] = nil
local ctn = player_statistics.MapsPlayed
player_statistics.MapsPlayed = ctn and ctn + 1 or 1
end
return player_statistics
end)
--- Used to format time in minute format
local function format_minutes(value)
return format_time(value*3600, {
long = true,
hours = true,
minutes = true
})
end
--- Used to format time into a clock
local function format_clock(value)
return format_time(value*3600, {
hours=true,
minutes=true,
seconds=false,
time=false,
string=true
})
end
--- Add MapsPlayed if it is enabled
if config.MapsPlayed then
Statistics:combine('MapsPlayed')
Event.add(defines.events.on_player_created, function(event)
local player = game.players[event.player_index]
new_players[player.name] = true
end)
end
--- Add Playtime and AfkTime if it is enabled
if config.Playtime or config.AfkTime then
local playtime, afk_time
if config.Playtime then playtime = Statistics:combine('Playtime') playtime:set_metadata{stringify=format_minutes, stringify_short=format_clock} end
if config.AfkTime then afk_time = Statistics:combine('AfkTime') afk_time:set_metadata{stringify=format_minutes, stringify_short=format_clock} end
Event.on_nth_tick(3600, function()
if game.tick == 0 then return end
for _, player in pairs(game.connected_players) do
if playtime then playtime:increment(player) end
if afk_time and player.afk_time > afk_required then afk_time:increment(player) end
end
end)
end
--- Add DistanceTravelled if it is enabled
if config.DistanceTravelled then
local stat = Statistics:combine('DistanceTravelled')
stat:set_metadata{unit=' tiles'}
Event.add(defines.events.on_player_changed_position, function(event)
local player = game.players[event.player_index]
if not player.valid or not player.connected or player.afk_time > afk_required then return end
stat:increment(player)
end)
end
--- Add MachinesRemoved and TreesDestroyed and config.OreMined if it is enabled
if config.MachinesRemoved or config.TreesDestroyed or config.OreMined then
local machines, trees, ore
if config.MachinesRemoved then machines = Statistics:combine('MachinesRemoved') end
if config.TreesDestroyed then trees = Statistics:combine('TreesDestroyed') end
if config.OreMined then ore = Statistics:combine('OreMined') end
local function on_event(event)
if not event.player_index then return end -- Check player is valid
local player = game.players[event.player_index]
if not player.valid or not player.connected then return end
local entity = event.entity -- Check entity is valid
if not entity.valid then return end
if entity.type == 'resource' then ore:increment(player)
elseif entity.type == 'tree' then trees:increment(player)
elseif entity.force == player.force then machines:increment(player) end
end
Event.add(defines.events.on_marked_for_deconstruction, on_event)
Event.add(defines.events.on_player_mined_entity, on_event)
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
stat:increment(player, floor(event.final_damage_amount))
end)
end
--- Add Kills if it is enabled
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
stat:increment(player)
end)
end
--- Add RocketsLaunched if it is enabled
if config.RocketsLaunched then
local stat = Statistics:combine('RocketsLaunched')
Event.add(defines.events.on_rocket_launched, function(event)
local silo = event.rocket_silo -- Check silo is valid
if not silo or not silo.valid then return end
local force = silo.force -- Check force is valid
if not force or not force.valid then return end
for _, player in pairs(force.connected_players) do
stat:increment(player)
end
end)
end
--- Add RocketsLaunched if it is enabled
if config.ResearchCompleted then
local stat = Statistics:combine('ResearchCompleted')
Event.add(defines.events.on_research_finished, function(event)
local research = event.research -- Check research is valid
if event.by_script or not research or not research.valid then return end
local force = research.force -- Check force is valid
if not force or not force.valid then return end
for _, player in pairs(force.connected_players) do
stat:increment(player)
end
end)
end
--- Add all the remaining statistics from the config
for statistic, event_name in pairs(config.counters) do
local stat = Statistics:combine(statistic)
Event.add(event_name, function(event)
if not event.player_index then return end
local player = game.players[event.player_index]
if not player.valid or not player.connected then return end
stat:increment(player)
end)
end

View File

@@ -0,0 +1,90 @@
--[[-- Commands Module - Tag
- Adds a command that allows players to have a custom tag after their name
@data Tag
]]
local Commands = require 'expcore.commands' --- @dep expcore.commands
local Roles = require 'expcore.roles' --- @dep expcore.roles
require 'config.expcore.command_general_parse'
require 'config.expcore.command_role_parse'
require 'config.expcore.command_color_parse'
--- Stores the tag for a player
local PlayerData = require 'expcore.player_data' --- @dep expcore.player_data
local PlayerTags = PlayerData.Settings:combine('Tag')
local PlayerTagColors = PlayerData.Settings:combine('TagColor')
PlayerTags:set_metadata{
permission = 'command/tag'
}
PlayerTagColors:set_metadata{
permission = 'command/tag-color'
}
local set_tag = function (player, tag, color)
if tag == nil or tag == '' then
player.tag = ''
elseif color then
player.tag = '- [color='.. color ..']'..tag..'[/color]'
else
player.tag = '- '..tag
end
end
--- When your tag is updated then apply the changes
PlayerTags:on_update(function(player_name, player_tag)
local player = game.players[player_name]
local player_tag_color = PlayerTagColors:get(player)
set_tag(player, player_tag, player_tag_color)
end)
--- When your tag color is updated then apply the changes
PlayerTagColors:on_update(function(player_name, player_tag_color)
local player = game.players[player_name]
local player_tag = PlayerTags:get(player)
set_tag(player, player_tag, player_tag_color)
end)
--- Sets your player tag.
-- @command tag
-- @tparam string tag the tag that will be after the name, there is a max length
Commands.new_command('tag', {'expcom-tag.description'}, 'Sets your player tag.')
:add_param('tag', false, 'string-max-length', 20)
:enable_auto_concat()
:register(function(player, tag)
PlayerTags:set(player, tag)
end)
--- Sets your player tag color.
-- @command tag
-- @tparam string color name.
Commands.new_command('tag-color', 'Sets your player tag color.')
:add_param('color', false, 'color')
:enable_auto_concat()
:register(function(player, color)
PlayerTagColors:set(player, color)
end)
--- Clears your tag. Or another player if you are admin.
-- @command tag-clear
-- @tparam[opt=self] LuaPlayer player the player to remove the tag from, nil will apply to self
Commands.new_command('tag-clear', {'expcom-tag.description-clear'}, 'Clears your tag. Or another player if you are admin.')
:add_param('player', true, 'player-role')
:set_defaults{player=function(player)
return player -- default is the user using the command
end}
:register(function(player, action_player)
if action_player.index == player.index then
-- no player given so removes your tag
PlayerTags:remove(action_player)
elseif Roles.player_allowed(player, 'command/clear-tag/always') then
-- player given and user is admin so clears that player's tag
PlayerTags:remove(action_player)
else
-- user is not admin and tried to clear another users tag
return Commands.error{'expcore-commands.unauthorized'}
end
end)

View File

@@ -0,0 +1,124 @@
local Event = require 'utils.event' --- @dep utils.event
local Global = require 'utils.global' --- @dep utils.global
local config = require 'config.advanced_start' --- @dep config.advanced_start
local use_silo_script = not config.disable_base_game_silo_script
local util = require("util")
local silo_script
if use_silo_script then
silo_script = require("silo-script")
end
local global = {}
Global.register(global, function(tbl)
global = tbl
end)
local created_items = function()
return
{
["iron-plate"] = 8,
["wood"] = 1,
["pistol"] = 1,
["firearm-magazine"] = 10,
["burner-mining-drill"] = 1,
["stone-furnace"] = 1
}
end
local respawn_items = function()
return
{
["pistol"] = 1,
["firearm-magazine"] = 10
}
end
if use_silo_script then
for k, v in pairs(silo_script.get_events()) do
Event.add(k, v)
end
end
Event.add(defines.events.on_player_created, function(event)
local player = game.players[event.player_index]
util.insert_safe(player, global.created_items)
local r = global.chart_distance or 200
player.force.chart(player.surface, {{player.position.x - r, player.position.y - r}, {player.position.x + r, player.position.y + r}})
if not global.skip_intro then
if game.is_multiplayer() then
player.print({"msg-intro"})
else
game.show_message_dialog{text = {"msg-intro"}}
end
end
if use_silo_script then
silo_script.on_event(event)
end
end)
Event.add(defines.events.on_player_respawned, function(event)
local player = game.players[event.player_index]
util.insert_safe(player, global.respawn_items)
if use_silo_script then
silo_script.on_event(event)
end
end)
if use_silo_script then
Event.on_load(function()
silo_script.on_load()
end)
end
Event.on_init(function()
global.created_items = created_items()
global.respawn_items = respawn_items()
if use_silo_script then
silo_script.on_init()
end
end)
if use_silo_script then
silo_script.add_remote_interface()
silo_script.add_commands()
end
remote.add_interface("freeplay",
{
get_created_items = function()
return global.created_items
end,
set_created_items = function(map)
global.created_items = map
end,
get_respawn_items = function()
return global.respawn_items
end,
set_respawn_items = function(map)
global.respawn_items = map
end,
set_skip_intro = function(bool)
global.skip_intro = bool
end,
set_chart_distance = function(value)
global.chart_distance = tonumber(value)
end,
set_disable_crashsite = function()
end,
get_ship_items = function()
return {}
end,
set_ship_items = function()
return
end,
get_debris_items = function ()
return {}
end,
set_debris_items = function ()
return
end
})

View File

@@ -0,0 +1,187 @@
local Event = require("utils.event")
local general = require("modules.graftorio.general")
local config = require("config.graftorio")
local lib = {}
lib.collect_production = function()
for _, force in pairs(game.forces) do
---@class ItemStats
---@field count number
---@class ProductionStatistics
---@field item_input table<string, ItemStats>
---@field item_output table<string, ItemStats>
---@field fluid_input table<string, ItemStats>
---@field fluid_output table<string, ItemStats>
---@field kill_input table<string, ItemStats>
---@field kill_output table<string, ItemStats>
---@field build_input table<string, ItemStats>
---@field build_output table<string, ItemStats>
local stats = {
item_input = {},
item_output = {},
fluid_input = {},
fluid_output = {},
kill_input = {},
kill_output = {},
build_input = {},
build_output = {},
}
for name, count in pairs(force.item_production_statistics.input_counts) do
local itemstats = stats.item_input[name] or {}
itemstats.count = count
stats.item_input[name] = itemstats
end
for name, count in pairs(force.item_production_statistics.output_counts) do
local itemstats = stats.item_output[name] or {}
itemstats.count = count
stats.item_output[name] = itemstats
end
for name, count in pairs(force.fluid_production_statistics.input_counts) do
local fluidstats = stats.fluid_input[name] or {}
fluidstats.count = count
stats.fluid_input[name] = fluidstats
end
for name, count in pairs(force.fluid_production_statistics.output_counts) do
local fluidstats = stats.fluid_output[name] or {}
fluidstats.count = count
stats.fluid_output[name] = fluidstats
end
for name, count in pairs(force.kill_count_statistics.input_counts) do
local killstats = stats.kill_input[name] or {}
killstats.count = count
stats.kill_input[name] = killstats
end
for name, count in pairs(force.kill_count_statistics.output_counts) do
local killstats = stats.kill_output[name] or {}
killstats.count = count
stats.kill_output[name] = killstats
end
for name, count in pairs(force.entity_build_count_statistics.input_counts) do
local buildstats = stats.build_input[name] or {}
buildstats.count = count
stats.build_input[name] = buildstats
end
for name, count in pairs(force.entity_build_count_statistics.output_counts) do
local buildstats = stats.build_output[name] or {}
buildstats.count = count
stats.build_output[name] = buildstats
end
general.data.output[force.name].production = stats
end
end
lib.collect_loginet = function()
for _, force in pairs(game.forces) do
---@class RobotStatistics
---@field all_construction_robots uint
---@field available_construction_robot uint
---@field all_logistic_robots uint
---@field available_logistic_robots uint
---@field charging_robot_count uint
---@field to_charge_robot_count uint
---@field items table<string, uint>
---@field pickups table<string, uint>
---@field deliveries table<string, uint>
local stats = {
all_construction_robots = 0,
available_construction_robots = 0,
all_logistic_robots = 0,
available_logistic_robots = 0,
charging_robot_count = 0,
to_charge_robot_count = 0,
items = {},
pickups = {},
deliveries = {},
}
for _, networks in pairs(force.logistic_networks) do
for _, network in pairs(networks) do
stats.available_construction_robots = network.available_construction_robots
stats.all_construction_robots = network.all_construction_robots
stats.available_logistic_robots = network.available_logistic_robots
stats.all_logistic_robots = network.all_logistic_robots
stats.charging_robot_count = 0
stats.to_charge_robot_count = 0
for _, cell in pairs(network.cells) do
stats.charging_robot_count = (stats.charging_robot_count) + cell.charging_robot_count
stats.to_charge_robot_count = (stats.to_charge_robot_count) + cell.to_charge_robot_count
end
if config.modules.logistorage then
for name, v in pairs(network.get_contents()) do
stats.items[name] = (stats.items[name] or 0) + v
end
-- pickups and deliveries of items
for _, point_list in pairs({ network.provider_points, network.requester_points, network.storage_points }) do
for _, point in pairs(point_list) do
for name, qty in pairs(point.targeted_items_pickup) do
stats.pickups[name] = (stats.pickups[name] or 0) + qty
end
for name, qty in pairs(point.targeted_items_deliver) do
stats.deliveries[name] = (stats.deliveries[name] or 0) + qty
end
end
end
end
end
end
general.data.output[force.name].robots = stats
end
end
---@class Research
---@field name string
---@field level uint
---@field progress double
Event.add(defines.events.on_research_finished, function(evt)
local research = evt.research
if not general.data.output[research.force.name] then general.data.output[research.force.name] = {} end
if not general.data.output[research.force.name].research then general.data.output[research.force.name].research = {} end
local force_research = general.data.output[research.force.name].research or {}
table.remove(force_research, 1)
general.data.output[research.force.name].research = force_research
end)
Event.add(defines.events.on_research_started, function(evt)
-- move queue up
local research = evt.research
if not general.data.output[research.force.name].research then general.data.output[research.force.name].research = {} end
local force_research = general.data.output[research.force.name].research or {}
table.remove(force_research, 1)
general.data.output[research.force.name].research = force_research
end)
Event.on_nth_tick(60, function()
for _, force in pairs(game.forces) do
if not general.data.output[force.name].research then general.data.output[force.name].research = {} end
local force_research = {}
-- this works even if the queue is disabled, but it will always be just 1 long in that case
for _, research in pairs(force.research_queue) do
table.insert(force_research, {
name = research.name,
level = research.level,
progress = force.get_saved_technology_progress(research) or 0,
})
end
general.data.output[force.name].research = force_research
end
end)
return lib

View File

@@ -0,0 +1,63 @@
local Event = require("utils.event")
local Global = require("utils.global")
local lib = {}
lib.data = {
output = {}
}
Global.register(lib.data, function(tbl)
lib.data = tbl
end)
---@class Statistics
---@field production ProductionStatistics?
---@field robots RobotStatistics?
---@field other OtherStatistics?
---@field research Research[]?
Event.on_init(function()
---@type table<string, Statistics>
lib.data.output = {}
for _, force in pairs(game.forces) do
lib.data.output[force.name] = {}
end
end)
---@class OtherStatistics
---@field tick uint
---@field evolution EvolutionStatistics
---@class EvolutionStatistics
---@field evolution_factor double
---@field evolution_factor_by_pollution double
---@field evolution_factor_by_time double
---@field evolution_factor_by_killing_spawners double
lib.collect_other = function()
for _, force in pairs(game.forces) do
---@type OtherStatistics
local other = lib.data.output[force.name].other or {}
other.evolution = {
evolution_factor = force.evolution_factor,
evolution_factor_by_pollution = force.evolution_factor_by_pollution,
evolution_factor_by_time = force.evolution_factor_by_time,
evolution_factor_by_killing_spawners = force.evolution_factor_by_killing_spawners
}
for k, v in pairs(other) do
lib.data.output[force.name].other[k] = v
end
end
end
Event.add(defines.events.on_force_created, function(evt)
lib.data.output[evt.force.name] = {}
end)
Event.add(defines.events.on_forces_merged, function(evt)
lib.data.output[evt.source_name] = nil
end)
return lib

View File

@@ -0,0 +1,27 @@
local Commands = require("expcore.commands")
local config = require("config.graftorio")
local statics = require("modules.graftorio.statics")
local general = require("modules.graftorio.general")
local forcestats = nil
if config.modules.forcestats then
forcestats = require("modules.graftorio.forcestats")
end
Commands.new_command("collectdata", "Collect data for RCON usage")
:add_param("location", true)
:register(function()
-- this must be first as it overwrites the stats
-- also makes the .other table for all forces
statics.collect_statics()
if config.modules.other then
general.collect_other()
end
if config.modules.forcestats then
---@cast forcestats -nil
forcestats.collect_production()
forcestats.collect_loginet()
end
rcon.print(game.table_to_json(general.data.output))
return Commands.success()
end)

View File

@@ -0,0 +1,35 @@
local general = require("modules.graftorio.general")
local lib = {}
---@class StaticStatistics
---@field tick uint
---@field online_players string[]
---@field mods table<string, string>
---@field seed table<string, uint>
lib.collect_statics = function()
local stats = {}
stats.tick = game.tick
stats.online_players = {}
for _, player in pairs(game.connected_players) do
table.insert(stats.online_players, player.name)
end
stats.mods = {}
for name, version in pairs(game.active_mods) do
stats.mods[name] = version
end
-- reason behind this is that the map gen settings can be changed during runtime so just get them fresh
stats.seed = {}
for _, surface in pairs(game.surfaces) do
stats.seed[surface.name] = surface.map_gen_settings.seed
end
for _, force in pairs(game.forces) do
general.data.output[force.name].other = stats
end
end
return lib

View File

@@ -0,0 +1,364 @@
--[[-- Gui Module - Autofill
- Adds a button to enable Autofill
@gui Autofill
@alias autofill
]]
local Game = require 'utils.game' -- @dep utils.game
local Gui = require 'expcore.gui' -- @dep expcore.gui
local Roles = require 'expcore.roles' -- @dep expcore.gui
local Global = require 'utils.global' -- @dep utils.global
local config = require 'config.gui.autofill' -- @dep config.gui.autofill
local Event = require 'utils.event' -- @dep utils.event
local table = require 'overrides.table' -- @dep overrides.table
local print_text = Game.print_floating_text -- (surface, position, text, color)
--- Table that stores if autofill is enabled or not
local autofill_player_settings = {}
Global.register(autofill_player_settings, function(tbl)
autofill_player_settings = tbl
end)
local autofill_container
local function rich_img(type, value)
return '[img='..type..'/'..value..']'
end
--- Toggle entity section visibility
-- @element toggle_item_button
local toggle_section =
Gui.element{
type = 'sprite-button',
sprite = 'utility/expand_dark',
hovered_sprite = 'utility/expand',
tooltip = {'autofill.toggle-section-tooltip'},
name = Gui.unique_static_name
}
:style(Gui.sprite_style(20))
:on_click(function(_, element, _)
local header_flow = element.parent
local flow_name = header_flow.caption
local flow = header_flow.parent.parent[flow_name]
if Gui.toggle_visible_state(flow) then
element.sprite = 'utility/collapse_dark'
element.hovered_sprite = 'utility/collapse'
element.tooltip = {'autofill.toggle-section-collapse-tooltip'}
else
element.sprite = 'utility/expand_dark'
element.hovered_sprite = 'utility/expand'
element.tooltip = {'autofill.toggle-section-tooltip'}
end
end)
--- Toggle enitity button, used for toggling autofill for the specific entity
-- All entity autofill settings will be ignored if its disabled
-- @element entity_toggle
local entity_toggle =
Gui.element(function(_, parent, entity_name)
return parent.add{
type = 'sprite-button',
sprite = 'utility/confirm_slot',
tooltip = {'autofill.toggle-entity-tooltip', rich_img('item', entity_name)},
style = 'shortcut_bar_button_green'
}
end)
:style(Gui.sprite_style(22))
:on_click(function(player, element, _)
local entity_name = string.match(element.parent.parent.name,'(.*)%-header')
if not autofill_player_settings[player.name] then return end
local setting = autofill_player_settings[player.name][entity_name]
if not setting then return end
if setting.enabled then
setting.enabled = false
element.sprite = 'utility/close_black'
element.style = 'shortcut_bar_button_red'
else
setting.enabled = true
element.sprite = 'utility/confirm_slot'
element.style = 'shortcut_bar_button_green'
end
-- Correct the button size
local style = element.style
style.padding = -2
style.height = 22
style.width = 22
end)
--- Draw a section header and main scroll
-- @element autofill_section_container
local section =
Gui.element(function(definition, parent, section_name, table_size)
-- Draw the header for the section
local header = Gui.header(
parent,
{'autofill.toggle-section-caption', rich_img('item', section_name), {'entity-name.'..section_name}},
{'autofill.toggle-section-tooltip'},
true,
section_name..'-header'
)
definition:triggers_events(header.parent.header_label)
-- Right aligned button to toggle the section
header.caption = section_name
entity_toggle(header, section_name)
toggle_section(header)
local section_table = parent.add{
type = 'table',
name = section_name,
column_count = table_size
}
section_table.visible = false
return definition:no_events(section_table)
end)
:on_click(function(_, element, event)
event.element = element.parent.alignment[toggle_section.name]
toggle_section:raise_event(event)
end)
--- Toggle item button, used for toggling autofill for the specific item
-- @element toggle_item_button
local toggle_item_button =
Gui.element(function(_, parent, item)
return parent.add{
type = 'sprite-button',
sprite = 'item/'..item.name,
tooltip = {'autofill.toggle-tooltip', rich_img('item', item.name), item.category},
style = 'shortcut_bar_button_red'
}
end)
:style(Gui.sprite_style(32, nil, { right_margin = -3 }))
:on_click(function(player, element)
local item_name = element.parent.tooltip
local entity_name = element.parent.parent.parent.name
if not autofill_player_settings[player.name] then return end
local setting = autofill_player_settings[player.name][entity_name]
if not setting then return end
local item = setting.items[item_name]
if not item then return end
if item.enabled then
item.enabled = false
element.style = 'shortcut_bar_button_red'
else
item.enabled = true
element.style = 'shortcut_bar_button_green'
end
-- Correct the button size
local style = element.style
style.right_margin = -3
style.padding = -2
style.height = 32
style.width = 32
end)
--- Amount text field for a autofill item
-- @element amount_textfield
local amount_textfield =
Gui.element(function(_, parent, item)
return parent.add{
type = 'textfield',
text = item.amount,
tooltip = {'autofill.amount-tooltip', item.category },
clear_and_focus_on_right_click = true,
numeric = true,
allow_decimal = false,
allow_negative = false
}
end)
:style{
maximal_width = 40,
height = 31,
padding = -2
}
:on_text_changed(function(player, element, _)
local value = tonumber(element.text)
if not value then value = 0 end
local clamped = math.clamp(value, 0, 1000)
local item_name = element.parent.tooltip
local entity_name = element.parent.parent.parent.name
if not autofill_player_settings[player.name] then return end
local setting = autofill_player_settings[player.name][entity_name]
if not setting then return end
local item = setting.items[item_name]
if not item then return end
item.amount = clamped
if clamped ~= value then
element.text = clamped
player.print{'autofill.invalid', item.amount, rich_img('item', item.name), rich_img('entity', entity_name) }
return
end
end)
--- Autofill setting, contains a button and a textbox
-- @element add_autofill_setting
local add_autofill_setting =
Gui.element(function(_, parent, item)
local toggle_flow = parent.add{ type = 'flow', name = 'toggle-setting-'..item.name, tooltip = item.name }
local amount_flow = parent.add{ type = 'flow', name = 'amount-setting-'..item.name, tooltip = item.name }
toggle_flow.style.padding = 0
amount_flow.style.padding = 0
toggle_item_button(toggle_flow, item)
amount_textfield(amount_flow, item)
end)
--- Autofill setting empty, contains filler button and textfield gui elements
-- @element add_empty_autofill_setting
local add_empty_autofill_setting =
Gui.element(function(_, parent)
local toggle_element = parent.add{
type = 'sprite-button'
}
toggle_element.style.right_margin = -3
toggle_element.style.width = 32
toggle_element.style.height = 32
toggle_element.enabled = false
local amount_element = parent.add{
type = 'textfield'
}
amount_element.style.maximal_width = 40
amount_element.style.height = 31
amount_element.style.padding = -2
amount_element.enabled = false
end)
--- Main gui container for the left flow
-- @element autofill_container
autofill_container =
Gui.element(function(definition, parent)
-- Draw the internal container
local container = Gui.container(parent, definition.name)
-- Draw the scroll container
local scroll_table = Gui.scroll_table(container, 400, 1, 'autofill-scroll-table')
-- Set the scroll panel to always show the scrollbar (not doing this will result in a changing gui size)
scroll_table.parent.vertical_scroll_policy = 'always'
-- Scroll panel has by default padding
scroll_table.parent.style.padding = 0
-- Remove the default gap that is added in a table between elements
scroll_table.style.vertical_spacing = 0
-- Center the first collumn in the table
scroll_table.style.column_alignments[1] = 'center'
-- Loop over each default entity config
for _, setting in pairs(config.default_entities) do
local table_sizes = {}
local tables = {}
-- Draw a section for the element
local entity_table = section(scroll_table, setting.entity, 3)
-- Add some padding around the table
entity_table.style.padding = 3
-- Make sure each collumn is alignment top center
entity_table.style.column_alignments[1] = 'top-center'
entity_table.style.column_alignments[2] = 'top-center'
entity_table.style.column_alignments[3] = 'top-center'
-- Loop over each item category
for _, category in pairs(config.categories) do
if not table_sizes[category] then table_sizes[category] = 0 end
-- Draw table
local category_table = entity_table.add{
type = 'table',
name = category..'-category',
column_count = 2
}
-- Add padding between each item
category_table.style.vertical_spacing = 1
tables[category] = category_table
-- Add item autofill setting gui elements to the table
for _, item in pairs(setting.items) do
if item.category == category then
add_autofill_setting(category_table, item)
table_sizes[category] = table_sizes[category] + 1
end
end
end
-- Add empty gui elements for the categories with less items than the other categories
local t = table.get_values(table_sizes)
table.sort(t)
local biggest = t[#t]
for category, size in pairs(table_sizes) do
for i=biggest-size,1,-1 do
add_empty_autofill_setting(tables[category])
end
end
end
-- Return the external container
return container.parent
end)
:static_name(Gui.unique_static_name)
:add_to_left_flow()
--- Button on the top flow used to toggle autofill container
-- @element autofill_toggle
Gui.left_toolbar_button(config.icon, {'autofill.main-tooltip'}, autofill_container, function(player)
return Roles.player_allowed(player, 'gui/autofill')
end)
--- When a player is created make sure they have the default autofill settings
Event.add(defines.events.on_player_created, function(event)
local player = game.players[event.player_index]
if not autofill_player_settings[player.name] then
autofill_player_settings[player.name] = table.deep_copy(config.default_entities)
end
end)
local function entity_build(event)
-- Check if player exists
local player = game.players[event.player_index]
if not player then
return
end
-- Check if the entity is in the config and enabled
local entity = event.created_entity
-- Check if player has settings
if not autofill_player_settings[player.name] then return end
local entity_settings = autofill_player_settings[player.name][entity.name]
-- Check if autofill for the entity is enabled
if not entity_settings then return end
if not entity_settings.enabled then return end
-- Get the inventory of the player
local player_inventory = player.get_main_inventory()
local text_position = { x = entity.position.x, y = entity.position.y }
-- Loop over all possible items to insert into the entity
for _, item in pairs(entity_settings.items) do
-- Check if the item is enabled or goto next item
if not item.enabled then goto end_item end
-- Get the inventory of the entity or goto next item
local entity_inventory = entity.get_inventory(item.inv)
if not entity_inventory then goto end_item end
local preferd_amount = item.amount
local item_amount = player_inventory.get_item_count(item.name)
if item_amount ~= 0 then
local inserted
text_position.y = text_position.y - 0.5
local color = { r = 0, g = 255, b = 0, a = 1}
if item_amount >= preferd_amount then
-- Can item be inserted? no, goto next item!
if not entity_inventory.can_insert{name=item.name, count=preferd_amount} then
goto end_item
end
inserted = entity_inventory.insert{name=item.name, count=preferd_amount}
else
inserted = entity_inventory.insert{name=item.name, count=item_amount}
color = { r = 255, g = 165, b = 0, a = 1}
end
player_inventory.remove{name=item.name, count=inserted}
print_text(entity.surface, text_position, {'autofill.inserted', inserted, rich_img('item', item.name), rich_img('entity', entity.name) }, color)
end
::end_item::
end
end
Event.add(defines.events.on_built_entity, entity_build)

View File

@@ -0,0 +1,392 @@
--[[-- Gui Module - Bonus
@gui Bonus
@alias bonus_container
]]
local Gui = require 'expcore.gui' --- @dep expcore.gui
local Roles = require 'expcore.roles' --- @dep expcore.roles
local Event = require 'utils.event' --- @dep utils.event
local config = require 'config.bonus' --- @dep config.bonus
local vlayer = require 'modules.control.vlayer'
local format_number = require('util').format_number --- @dep util
local bonus_container
local function bonus_gui_pts_needed(player)
local frame = Gui.get_left_element(player, bonus_container)
local disp = frame.container['bonus_st_2'].disp.table
local total = 0
for k, v in pairs(config.conversion) do
total = total + (disp['bonus_display_' .. k .. '_slider'].slider_value / config.player_bonus[v].cost_scale * config.player_bonus[v].cost)
end
total = total + (disp['bonus_display_personal_battery_recharge_slider'].slider_value / config.player_special_bonus['personal_battery_recharge'].cost_scale * config.player_special_bonus['personal_battery_recharge'].cost)
return total
end
local function apply_bonus(player)
if not Roles.player_allowed(player, 'gui/bonus') then
for k, v in pairs(config.player_bonus) do
player[k] = 0
if v.combined_bonus then
for i=1, #v.combined_bonus do
player[v.combined_bonus[i]] = 0
end
end
end
return
end
if not player.character then
return
end
local frame = Gui.get_left_element(player, bonus_container)
local disp = frame.container['bonus_st_2'].disp.table
for k, v in pairs(config.conversion) do
player[v] = disp['bonus_display_' .. k .. '_slider'].slider_value
if config.player_bonus[v].combined_bonus then
for i=1, #config.player_bonus[v].combined_bonus do
player[config.player_bonus[v].combined_bonus[i]] = disp['bonus_display_' .. k .. '_slider'].slider_value
end
end
end
end
local function apply_periodic_bonus(player)
if not Roles.player_allowed(player, 'gui/bonus') then
return
end
if not player.character then
return
end
local frame = Gui.get_left_element(player, bonus_container)
local disp = frame.container['bonus_st_2'].disp.table
if vlayer.get_statistics()['energy_sustained'] > 0 then
local armor = player.get_inventory(defines.inventory.character_armor)[1].grid
if armor then
local slider = disp['bonus_display_personal_battery_recharge_slider'].slider_value * 100000 * config.player_special_bonus_rate / 6
for i=1, #armor.equipment do
if armor.equipment[i].energy < armor.equipment[i].max_energy then
local energy_required = math.min(math.floor(armor.equipment[i].max_energy - armor.equipment[i].energy), vlayer.get_statistics()['energy_storage'], slider)
armor.equipment[i].energy = armor.equipment[i].energy + energy_required
vlayer.energy_changed(- energy_required)
slider = slider - energy_required
end
end
end
end
end
--- Control label for the bonus points available
-- @element bonus_gui_control_pts_a
local bonus_gui_control_pts_a =
Gui.element{
type = 'label',
name = 'bonus_control_pts_a',
caption = {'bonus.control-pts-a'},
style = 'heading_2_label'
}:style{
width = config.gui_display_width['half']
}
local bonus_gui_control_pts_a_count =
Gui.element{
type = 'label',
name = 'bonus_control_pts_a_count',
caption = config.pts.base,
style = 'heading_2_label'
}:style{
width = config.gui_display_width['half']
}
--- Control label for the bonus points needed
-- @element bonus_gui_control_pts_n
local bonus_gui_control_pts_n =
Gui.element{
type = 'label',
name = 'bonus_control_pts_n',
caption = {'bonus.control-pts-n'},
style = 'heading_2_label'
}:style{
width = config.gui_display_width['half']
}
local bonus_gui_control_pts_n_count =
Gui.element{
type = 'label',
name = 'bonus_control_pts_n_count',
caption = '0',
style = 'heading_2_label'
}:style{
width =config.gui_display_width['half']
}
--- Control label for the bonus points remaining
-- @element bonus_gui_control_pts_r
local bonus_gui_control_pts_r =
Gui.element{
type = 'label',
name = 'bonus_control_pts_r',
caption = {'bonus.control-pts-r'},
style = 'heading_2_label'
}:style{
width = config.gui_display_width['half']
}
local bonus_gui_control_pts_r_count =
Gui.element{
type = 'label',
name = 'bonus_control_pts_r_count',
caption = '0',
style = 'heading_2_label'
}:style{
width = config.gui_display_width['half']
}
--- A button used for pts calculations
-- @element bonus_gui_control_refresh
local bonus_gui_control_reset =
Gui.element{
type = 'button',
name = Gui.unique_static_name,
caption = {'bonus.control-reset'}
}:style{
width = config.gui_display_width['half']
}:on_click(function(player, element, _)
local frame = Gui.get_left_element(player, bonus_container)
local disp = frame.container['bonus_st_2'].disp.table
for k, v in pairs(config.conversion) do
local s = 'bonus_display_' .. k .. '_slider'
disp[s].slider_value = config.player_bonus[v].value
if config.player_bonus[v].is_percentage then
disp[disp[s].tags.counter].caption = format_number(disp[s].slider_value * 100) .. ' %'
else
disp[disp[s].tags.counter].caption = format_number(disp[s].slider_value)
end
end
local slider = disp['bonus_display_personal_battery_recharge_slider']
slider.slider_value = config.player_special_bonus['personal_battery_recharge'].value
disp[slider.tags.counter].caption = format_number(slider.slider_value)
local r = bonus_gui_pts_needed(player)
element.parent[bonus_gui_control_pts_n_count.name].caption = r
element.parent[bonus_gui_control_pts_r_count.name].caption = tonumber(element.parent[bonus_gui_control_pts_a_count.name].caption) - r
end)
--- A button used for pts apply
-- @element bonus_gui_control_apply
local bonus_gui_control_apply =
Gui.element{
type = 'button',
name = Gui.unique_static_name,
caption = {'bonus.control-apply'}
}:style{
width = config.gui_display_width['half']
}:on_click(function(player, element, _)
local n = bonus_gui_pts_needed(player)
element.parent[bonus_gui_control_pts_n_count.name].caption = n
local r = tonumber(element.parent[bonus_gui_control_pts_a_count.name].caption) - n
element.parent[bonus_gui_control_pts_r_count.name].caption = r
if r >= 0 then
apply_bonus(player)
end
end)
--- A vertical flow containing all the bonus control
-- @element bonus_control_set
local bonus_control_set =
Gui.element(function(_, parent, name)
local bonus_set = parent.add{type='flow', direction='vertical', name=name}
local disp = Gui.scroll_table(bonus_set, config.gui_display_width['half'] * 2, 2, 'disp')
bonus_gui_control_pts_a(disp)
bonus_gui_control_pts_a_count(disp)
bonus_gui_control_pts_n(disp)
bonus_gui_control_pts_n_count(disp)
bonus_gui_control_pts_r(disp)
bonus_gui_control_pts_r_count(disp)
bonus_gui_control_reset(disp)
bonus_gui_control_apply(disp)
return bonus_set
end)
--- Display group
-- @element bonus_gui_slider
local bonus_gui_slider =
Gui.element(function(_definition, parent, name, caption, tooltip, bonus)
local label = parent.add{
type = 'label',
caption = caption,
tooltip = tooltip,
style = 'heading_2_label'
}
label.style.width = config.gui_display_width['label']
local value = bonus.value
if bonus.is_percentage then
value = format_number(value * 100) .. ' %'
else
value = format_number(value)
end
local slider = parent.add{
type = 'slider',
name = name .. '_slider',
value = bonus.value,
maximum_value = bonus.max,
value_step = bonus.scale,
discrete_values = true,
style = 'notched_slider',
tags = {
counter = name .. '_count',
is_percentage = bonus.is_percentage
}
}
slider.style.width = config.gui_display_width['slider']
slider.style.horizontally_stretchable = true
local count = parent.add{
type = 'label',
name = name .. '_count',
caption = value,
style = 'heading_2_label',
}
count.style.width = config.gui_display_width['count']
return slider
end)
:on_value_changed(function(player, element, _event)
if element.tags.is_percentage then
element.parent[element.tags.counter].caption = format_number(element.slider_value * 100) .. ' %'
else
element.parent[element.tags.counter].caption = format_number(element.slider_value)
end
local r = bonus_gui_pts_needed(player)
local frame = Gui.get_left_element(player, bonus_container)
local disp = frame.container['bonus_st_1'].disp.table
disp[bonus_gui_control_pts_n_count.name].caption = r
disp[bonus_gui_control_pts_r_count.name].caption = tonumber(disp[bonus_gui_control_pts_a_count.name].caption) - r
end)
--- A vertical flow containing all the bonus data
-- @element bonus_data_set
local bonus_data_set =
Gui.element(function(_, parent, name)
local bonus_set = parent.add{type='flow', direction='vertical', name=name}
local disp = Gui.scroll_table(bonus_set, config.gui_display_width['half'] * 2, 3, 'disp')
for k, v in pairs(config.conversion) do
bonus_gui_slider(disp, 'bonus_display_' .. k, {'bonus.display-' .. k}, {'bonus.display-' .. k .. '-tooltip'}, config.player_bonus[v])
end
bonus_gui_slider(disp, 'bonus_display_personal_battery_recharge', {'bonus.display-personal-battery-recharge'}, {'bonus.display-personal-battery-recharge-tooltip'}, config.player_special_bonus['personal_battery_recharge'])
return bonus_set
end)
--- The main container for the bonus gui
-- @element bonus_container
bonus_container =
Gui.element(function(definition, parent)
local player = Gui.get_player_from_element(parent)
local container = Gui.container(parent, definition.name, config.gui_display_width['half'] * 2)
bonus_control_set(container, 'bonus_st_1')
bonus_data_set(container, 'bonus_st_2')
local frame = Gui.get_left_element(player, bonus_container)
local disp = frame.container['bonus_st_1'].disp.table
local n = bonus_gui_pts_needed(player)
disp[bonus_gui_control_pts_n_count.name].caption = n
local r = tonumber(disp[bonus_gui_control_pts_a_count.name].caption) - n
disp[bonus_gui_control_pts_r_count.name].caption = r
apply_bonus(player)
return container.parent
end)
:static_name(Gui.unique_static_name)
:add_to_left_flow()
--- Button on the top flow used to toggle the bonus container
-- @element toggle_left_element
Gui.left_toolbar_button('item/exoskeleton-equipment', {'bonus.main-tooltip'}, bonus_container, function(player)
return Roles.player_allowed(player, 'gui/bonus')
end)
Event.add(defines.events.on_player_created, function(event)
if event.player_index ~= 1 then
return
end
for k, v in pairs(config.force_bonus) do
game.players[event.player_index].force[k] = v.value
end
for k, v in pairs(config.surface_bonus) do
game.players[event.player_index].surface[k] = v.value
end
end)
Event.add(Roles.events.on_role_assigned, function(event)
apply_bonus(game.players[event.player_index])
end)
Event.add(Roles.events.on_role_unassigned, function(event)
apply_bonus(game.players[event.player_index])
end)
--- When a player respawns re-apply bonus
Event.add(defines.events.on_player_respawned, function(event)
local player = game.players[event.player_index]
local frame = Gui.get_left_element(player, bonus_container)
local disp = frame.container['bonus_st_1'].disp.table
local n = bonus_gui_pts_needed(player)
disp[bonus_gui_control_pts_n_count.name].caption = n
local r = tonumber(disp[bonus_gui_control_pts_a_count.name].caption) - n
disp[bonus_gui_control_pts_r_count.name].caption = r
if r >= 0 then
apply_bonus(player)
end
end)
--- When a player dies allow them to have instant respawn
Event.add(defines.events.on_player_died, function(event)
local player = game.players[event.player_index]
if Roles.player_has_flag(player, 'instant-respawn') then
player.ticks_to_respawn = 120
end
end)
Event.on_nth_tick(config.player_special_bonus_rate, function(_)
for _, player in pairs(game.connected_players) do
apply_periodic_bonus(player)
end
end)

View File

@@ -0,0 +1,114 @@
local Gui = require 'utils.gui' --- @dep utils.gui
local Model = require 'modules.gui.debug.model' --- @dep modules.gui.debug.model
local Color = require 'utils.color_presets' --- @dep utils.color_presets
local dump = Model.dump
local Public = {}
local ignore = {
_G = true,
assert = true,
collectgarbage = true,
error = true,
getmetatable = true,
ipairs = true,
load = true,
loadstring = true,
next = true,
pairs = true,
pcall = true,
print = true,
rawequal = true,
rawlen = true,
rawget = true,
rawset = true,
select = true,
setmetatable = true,
tonumber = true,
tostring = true,
type = true,
xpcall = true,
_VERSION = true,
['module'] = true,
require = true,
package = true,
unpack = true,
table = true,
string = true,
bit32 = true,
math = true,
debug = true,
serpent = true,
log = true,
table_size = true,
global = true,
remote = true,
commands = true,
settings = true,
rcon = true,
script = true,
util = true,
mod_gui = true,
game = true,
rendering = true
}
local header_name = Gui.uid_name()
local left_panel_name = Gui.uid_name()
local right_panel_name = Gui.uid_name()
Public.name = '_G'
function Public.show(container)
local main_flow = container.add {type = 'flow', direction = 'horizontal'}
local left_panel = main_flow.add {type = 'scroll-pane', name = left_panel_name}
local left_panel_style = left_panel.style
left_panel_style.width = 300
for key, value in pairs(_G) do
if not ignore[key] then
local header =
left_panel.add({type = 'flow'}).add {type = 'label', name = header_name, caption = tostring(key)}
Gui.set_data(header, value)
end
end
local right_panel = main_flow.add {type = 'text-box', name = right_panel_name}
right_panel.read_only = true
right_panel.selectable = true
local right_panel_style = right_panel.style
right_panel_style.vertically_stretchable = true
right_panel_style.horizontally_stretchable = true
right_panel_style.maximal_width = 1000
right_panel_style.maximal_height = 1000
Gui.set_data(left_panel, {right_panel = right_panel, selected_header = nil})
end
Gui.on_click(
header_name,
function(event)
local element = event.element
local value = Gui.get_data(element)
local left_panel = element.parent.parent
local left_panel_data = Gui.get_data(left_panel)
local right_panel = left_panel_data.right_panel
local selected_header = left_panel_data.selected_header
if selected_header then
selected_header.style.font_color = Color.white
end
element.style.font_color = Color.orange
left_panel_data.selected_header = element
local content = dump(value)
right_panel.text = content
end
)
return Public

View File

@@ -0,0 +1,169 @@
local Event = require 'utils.event'
local table = require 'overrides.table'
local Gui = require 'utils.gui'
local Model = require 'modules.gui.debug.model'
local format = string.format
local insert = table.insert
local events = defines.events
-- Constants
local events_to_keep = 10
-- Local vars
local Public = {
name = 'Events'
}
local name_lookup = {}
-- GUI names
local checkbox_name = Gui.uid_name()
local filter_name = Gui.uid_name()
local clear_filter_name = Gui.uid_name()
-- global tables
local enabled = {}
local last_events = {}
global.debug_event_view = {
enabled = enabled,
last_events = last_events,
filter = ''
}
function Public.on_open_debug()
local tbl = global.debug_event_view
if tbl then
enabled = tbl.enabled
last_events = tbl.last_events
else
enabled = {}
last_events = {}
global.debug_event_view = {
enabled = enabled,
last_events = last_events
}
end
Public.on_open_debug = nil
end
-- Local functions
local function event_callback(event)
local id = event.name
if not enabled[id] then
return
end
local name = name_lookup[id]
if not last_events[name] then
last_events[name] = {}
end
insert(last_events[name], 1, event)
last_events[name][events_to_keep + 1] = nil
event.name = nil
local str = format('%s (id = %s): %s', name, id, Model.dump(event))
game.print(str)
log(str)
end
local function on_gui_checked_state_changed(event)
local element = event.element
local name = element.caption
local id = events[name]
local state = element.state and true or false
element.state = state
if state then
enabled[id] = true
else
enabled[id] = false
end
end
-- GUI
-- Create a table with events sorted by their names
local grid_builder = {}
for name, _ in pairs(events) do
grid_builder[#grid_builder + 1] = name
end
table.sort(grid_builder)
local function redraw_event_table(gui_table, filter)
for _, event_name in pairs(grid_builder) do
if filter == '' or event_name:find(filter) then
local index = events[event_name]
gui_table.add({type = 'flow'}).add {
name = checkbox_name,
type = 'checkbox',
state = enabled[index] or false,
caption = event_name
}
end
end
end
function Public.show(container)
local filter = global.debug_event_view.filter
local main_frame_flow = container.add({type = 'flow', direction = 'vertical'})
local filter_flow = main_frame_flow.add({type = 'flow', direction = 'horizontal'})
filter_flow.add({type = 'label', caption = 'filter'})
local filter_textfield = filter_flow.add({type = 'textfield', name = filter_name, text = filter})
local clear_button = filter_flow.add({type = 'button', name = clear_filter_name, caption = 'clear'})
local scroll_pane = main_frame_flow.add({type = 'scroll-pane'})
local gui_table = scroll_pane.add({type = 'table', column_count = 3, draw_horizontal_lines = true})
Gui.set_data(filter_textfield, gui_table)
Gui.set_data(clear_button, {gui_table = gui_table, filter_textfield = filter_textfield})
redraw_event_table(gui_table, filter)
end
Gui.on_checked_state_changed(checkbox_name, on_gui_checked_state_changed)
Gui.on_text_changed(
filter_name,
function(event)
local element = event.element
local gui_table = Gui.get_data(element)
local filter = element.text:gsub(' ', '_')
global.debug_event_view.filter = filter
element.text = filter
gui_table.clear()
redraw_event_table(gui_table, filter)
end
)
Gui.on_click(
clear_filter_name,
function(event)
local element = event.element
local data = Gui.get_data(element)
local filter_textfield = data.filter_textfield
local gui_table = data.gui_table
filter_textfield.text = ''
global.debug_event_view.filter = ''
gui_table.clear()
redraw_event_table(gui_table, '')
end
)
-- Event registers (TODO: turn to removable hooks.. maybe)
for name, id in pairs(events) do
name_lookup[id] = name
Event.add(id, event_callback)
end
return Public

View File

@@ -0,0 +1,130 @@
local Gui = require 'utils.gui' --- @dep utils.gui
local Datastore = require 'expcore.datastore' --- @dep expcore.datastore
local Color = require 'utils.color_presets' --- @dep utils.color_presets
local Model = require 'modules.gui.debug.model' --- @dep modules.gui.debug.model
local dump = Model.dump
local concat = table.concat
local Public = {}
local header_name = Gui.uid_name()
local left_panel_name = Gui.uid_name()
local right_panel_name = Gui.uid_name()
local input_text_box_name = Gui.uid_name()
local refresh_name = Gui.uid_name()
Public.name = 'Datastore'
function Public.show(container)
local main_flow = container.add {type = 'flow', direction = 'horizontal'}
local left_panel = main_flow.add {type = 'scroll-pane', name = left_panel_name}
local left_panel_style = left_panel.style
left_panel_style.width = 300
for name in pairs(table.keysort(Datastore.debug())) do
local header = left_panel.add({type = 'flow'}).add {type = 'label', name = header_name, caption = name}
Gui.set_data(header, name)
end
local right_flow = main_flow.add {type = 'flow', direction = 'vertical'}
local right_top_flow = right_flow.add {type = 'flow', direction = 'horizontal'}
local input_text_box = right_top_flow.add {type = 'text-box', name = input_text_box_name}
local input_text_box_style = input_text_box.style
input_text_box_style.horizontally_stretchable = true
input_text_box_style.height = 32
input_text_box_style.maximal_width = 1000
local refresh_button =
right_top_flow.add {type = 'sprite-button', name = refresh_name, sprite = 'utility/reset', tooltip = 'refresh'}
local refresh_button_style = refresh_button.style
refresh_button_style.width = 32
refresh_button_style.height = 32
local right_panel = right_flow.add {type = 'text-box', name = right_panel_name}
right_panel.read_only = true
right_panel.selectable = true
local right_panel_style = right_panel.style
right_panel_style.vertically_stretchable = true
right_panel_style.horizontally_stretchable = true
right_panel_style.maximal_width = 1000
right_panel_style.maximal_height = 1000
local data = {
right_panel = right_panel,
input_text_box = input_text_box,
selected_header = nil
}
Gui.set_data(input_text_box, data)
Gui.set_data(left_panel, data)
Gui.set_data(refresh_button, data)
end
Gui.on_click(
header_name,
function(event)
local element = event.element
local tableName = Gui.get_data(element)
local left_panel = element.parent.parent
local data = Gui.get_data(left_panel)
local right_panel = data.right_panel
local selected_header = data.selected_header
local input_text_box = data.input_text_box
if selected_header then
selected_header.style.font_color = Color.white
end
element.style.font_color = Color.orange
data.selected_header = element
input_text_box.text = tableName
input_text_box.style.font_color = Color.black
local content = Datastore.debug(tableName)
local content_string = {}
for key, value in pairs(content) do
content_string[#content_string+1] = key:gsub('^%l', string.upper)..' = '..dump(value)
end
right_panel.text = concat(content_string, '\n')
end
)
local function update_dump(text_input, data)
local content = Datastore.debug(text_input.text)
local content_string = {}
for key, value in pairs(content) do
content_string[#content_string+1] = key:gsub('^%l', string.upper)..' = '..dump(value)
end
data.right_panel.text = concat(content_string, '\n')
end
Gui.on_text_changed(
input_text_box_name,
function(event)
local element = event.element
local data = Gui.get_data(element)
update_dump(element, data)
end
)
Gui.on_click(
refresh_name,
function(event)
local element = event.element
local data = Gui.get_data(element)
local input_text_box = data.input_text_box
update_dump(input_text_box, data)
end
)
return Public

View File

@@ -0,0 +1,128 @@
local Gui = require 'utils.gui' --- @dep utils.gui
local ExpGui = require 'expcore.gui' --- @dep utils.global
local Color = require 'utils.color_presets' --- @dep utils.color_presets
local Model = require 'modules.gui.debug.model' --- @dep modules.gui.debug.model
local dump = Model.dump
local dump_text = Model.dump_text
local concat = table.concat
local Public = {}
local header_name = Gui.uid_name()
local left_panel_name = Gui.uid_name()
local right_panel_name = Gui.uid_name()
local input_text_box_name = Gui.uid_name()
local refresh_name = Gui.uid_name()
Public.name = 'Elements'
function Public.show(container)
local main_flow = container.add {type = 'flow', direction = 'horizontal'}
local left_panel = main_flow.add {type = 'scroll-pane', name = left_panel_name}
local left_panel_style = left_panel.style
left_panel_style.width = 300
for element_id, file_path in pairs(ExpGui.file_paths) do
local header = left_panel.add({type = 'flow'}).add {type = 'label', name = header_name, caption = element_id..' - '..file_path}
Gui.set_data(header, element_id)
end
local right_flow = main_flow.add {type = 'flow', direction = 'vertical'}
local right_top_flow = right_flow.add {type = 'flow', direction = 'horizontal'}
local input_text_box = right_top_flow.add {type = 'text-box', name = input_text_box_name}
local input_text_box_style = input_text_box.style
input_text_box_style.horizontally_stretchable = true
input_text_box_style.height = 32
input_text_box_style.maximal_width = 1000
local refresh_button =
right_top_flow.add {type = 'sprite-button', name = refresh_name, sprite = 'utility/reset', tooltip = 'refresh'}
local refresh_button_style = refresh_button.style
refresh_button_style.width = 32
refresh_button_style.height = 32
local right_panel = right_flow.add {type = 'text-box', name = right_panel_name}
right_panel.read_only = true
right_panel.selectable = true
local right_panel_style = right_panel.style
right_panel_style.vertically_stretchable = true
right_panel_style.horizontally_stretchable = true
right_panel_style.maximal_width = 1000
right_panel_style.maximal_height = 1000
local data = {
right_panel = right_panel,
input_text_box = input_text_box,
selected_header = nil
}
Gui.set_data(input_text_box, data)
Gui.set_data(left_panel, data)
Gui.set_data(refresh_button, data)
end
Gui.on_click(
header_name,
function(event)
local element = event.element
local element_id = Gui.get_data(element)
local left_panel = element.parent.parent
local data = Gui.get_data(left_panel)
local right_panel = data.right_panel
local selected_header = data.selected_header
local input_text_box = data.input_text_box
if selected_header then
selected_header.style.font_color = Color.white
end
element.style.font_color = Color.orange
data.selected_header = element
input_text_box.text = concat {'Gui.defines[', element_id, ']'}
input_text_box.style.font_color = Color.black
local content = dump(ExpGui.debug_info[element_id]) or 'nil'
right_panel.text = content
end
)
local function update_dump(text_input, data, player)
local suc, ouput = dump_text(text_input.text, player)
if not suc then
text_input.style.font_color = Color.red
else
text_input.style.font_color = Color.black
data.right_panel.text = ouput
end
end
Gui.on_text_changed(
input_text_box_name,
function(event)
local element = event.element
local data = Gui.get_data(element)
update_dump(element, data, event.player)
end
)
Gui.on_click(
refresh_name,
function(event)
local element = event.element
local data = Gui.get_data(element)
local input_text_box = data.input_text_box
update_dump(input_text_box, data, event.player)
end
)
return Public

View File

@@ -0,0 +1,133 @@
local Gui = require 'utils.gui' --- @dep utils.gui
local Model = require 'modules.gui.debug.model' --- @dep modules.gui.debug.model
local Color = require 'utils.color_presets' --- @dep utils.color_presets
local dump = Model.dump
local dump_text = Model.dump_text
local concat = table.concat
local Public = {}
local ignore = {tokens = true, data_store = true, datastores = true}
local header_name = Gui.uid_name()
local left_panel_name = Gui.uid_name()
local right_panel_name = Gui.uid_name()
local input_text_box_name = Gui.uid_name()
local refresh_name = Gui.uid_name()
Public.name = 'global'
function Public.show(container)
local main_flow = container.add {type = 'flow', direction = 'horizontal'}
local left_panel = main_flow.add {type = 'scroll-pane', name = left_panel_name}
local left_panel_style = left_panel.style
left_panel_style.width = 300
for key, _ in pairs(global) do
if not ignore[key] then
local header =
left_panel.add({type = 'flow'}).add {type = 'label', name = header_name, caption = tostring(key)}
Gui.set_data(header, key)
end
end
local right_flow = main_flow.add {type = 'flow', direction = 'vertical'}
local right_top_flow = right_flow.add {type = 'flow', direction = 'horizontal'}
local input_text_box = right_top_flow.add {type = 'text-box', name = input_text_box_name}
local input_text_box_style = input_text_box.style
input_text_box_style.horizontally_stretchable = true
input_text_box_style.height = 32
input_text_box_style.maximal_width = 1000
local refresh_button =
right_top_flow.add {type = 'sprite-button', name = refresh_name, sprite = 'utility/reset', tooltip = 'refresh'}
local refresh_button_style = refresh_button.style
refresh_button_style.width = 32
refresh_button_style.height = 32
local right_panel = right_flow.add {type = 'text-box', name = right_panel_name}
right_panel.read_only = true
right_panel.selectable = true
local right_panel_style = right_panel.style
right_panel_style.vertically_stretchable = true
right_panel_style.horizontally_stretchable = true
right_panel_style.maximal_width = 1000
right_panel_style.maximal_height = 1000
local data = {
right_panel = right_panel,
input_text_box = input_text_box,
selected_header = nil,
selected_token_id = nil
}
Gui.set_data(input_text_box, data)
Gui.set_data(left_panel, data)
Gui.set_data(refresh_button, data)
end
Gui.on_click(
header_name,
function(event)
local element = event.element
local key = Gui.get_data(element)
local left_panel = element.parent.parent
local data = Gui.get_data(left_panel)
local right_panel = data.right_panel
local selected_header = data.selected_header
local input_text_box = data.input_text_box
if selected_header then
selected_header.style.font_color = Color.white
end
element.style.font_color = Color.orange
data.selected_header = element
input_text_box.text = concat {"global['", key, "']"}
input_text_box.style.font_color = Color.black
local content = dump(global[key]) or 'nil'
right_panel.text = content
end
)
local function update_dump(text_input, data, player)
local suc, ouput = dump_text(text_input.text, player)
if not suc then
text_input.style.font_color = Color.red
else
text_input.style.font_color = Color.black
data.right_panel.text = ouput
end
end
Gui.on_text_changed(
input_text_box_name,
function(event)
local element = event.element
local data = Gui.get_data(element)
update_dump(element, data, event.player)
end
)
Gui.on_click(
refresh_name,
function(event)
local element = event.element
local data = Gui.get_data(element)
local input_text_box = data.input_text_box
update_dump(input_text_box, data, event.player)
end
)
return Public

View File

@@ -0,0 +1,113 @@
local Gui = require 'utils.gui' --- @dep utils.gui
local Color = require 'utils.color_presets' --- @dep utils.color_presets
local Public = {}
local pages = {
require 'modules.gui.debug.redmew_global_view',
require 'modules.gui.debug.expcore_datastore_view',
require 'modules.gui.debug.expcore_gui_view',
require 'modules.gui.debug.global_view',
require 'modules.gui.debug.package_view',
require 'modules.gui.debug._g_view',
require 'modules.gui.debug.event_view'
}
local main_frame_name = Gui.uid_name()
local close_name = Gui.uid_name()
local tab_name = Gui.uid_name()
function Public.open_dubug(player)
for i = 1, #pages do
local page = pages[i]
local callback = page.on_open_debug
if callback then
callback()
end
end
local center = player.gui.center
local frame = center[main_frame_name]
if frame then
return
end
--[[
local screen_element = player.gui.screen
frame = screen_element.add{type = 'frame', name = main_frame_name, caption = 'Debuggertron 3000'}
frame.style.size = {900, 600}
frame.auto_center = true
]]
frame = center.add {type = 'frame', name = main_frame_name, caption = 'Debuggertron 3002', direction = 'vertical'}
local frame_style = frame.style
frame_style.height = 600
frame_style.width = 900
local tab_flow = frame.add {type = 'flow', direction = 'horizontal'}
local container = frame.add {type = 'flow'}
container.style.vertically_stretchable = true
local data = {}
for i = 1, #pages do
local page = pages[i]
local tab_button = tab_flow.add({type = 'flow'}).add {type = 'button', name = tab_name, caption = page.name}
local tab_button_style = tab_button.style
Gui.set_data(tab_button, {index = i, frame_data = data})
if i == 1 then
tab_button_style.font_color = Color.orange
data.selected_index = i
data.selected_tab_button = tab_button
data.container = container
Gui.set_data(frame, data)
page.show(container)
end
end
frame.add {type = 'button', name = close_name, caption = 'Close'}
end
Gui.on_click(
tab_name,
function(event)
local element = event.element
local data = Gui.get_data(element)
local index = data.index
local frame_data = data.frame_data
local selected_index = frame_data.selected_index
if selected_index == index then
return
end
local selected_tab_button = frame_data.selected_tab_button
selected_tab_button.style.font_color = Color.black
frame_data.selected_tab_button = element
frame_data.selected_index = index
element.style.font_color = Color.orange
local container = frame_data.container
Gui.clear(container)
pages[index].show(container)
end
)
Gui.on_click(
close_name,
function(event)
local frame = event.player.gui.center[main_frame_name]
if frame then
Gui.destroy(frame)
end
end
)
return Public

View File

@@ -0,0 +1,147 @@
local Gui = require 'utils.gui' --- @dep utils.gui
local table = require 'overrides.table' --- @dep overrides.table
local gui_names = Gui.names
local type = type
local concat = table.concat
local inspect = table.inspect
local pcall = pcall
local loadstring = loadstring ---@diagnostic disable-line
local rawset = rawset
local Public = {}
local luaObject = {'{', nil, ", name = '", nil, "'}"}
local luaPlayer = {"{LuaPlayer, name = '", nil, "', index = ", nil, '}'}
local luaEntity = {"{LuaEntity, name = '", nil, "', unit_number = ", nil, '}'}
local luaGuiElement = {"{LuaGuiElement, name = '", nil, "'}"}
local function get(obj, prop)
return obj[prop]
end
local function get_name_safe(obj)
local s, r = pcall(get, obj, 'name')
if not s then
return 'nil'
else
return r or 'nil'
end
end
local function get_lua_object_type_safe(obj)
local s, r = pcall(get, obj, 'help')
if not s then
return
end
return r():match('Lua%a+')
end
local function inspect_process(item)
if type(item) ~= 'table' or type(item.__self) ~= 'userdata' then
return item
end
local suc, valid = pcall(get, item, 'valid')
if not suc then
-- no 'valid' property
return get_lua_object_type_safe(item) or '{NoHelp LuaObject}'
end
if not valid then
return '{Invalid LuaObject}'
end
local obj_type = get_lua_object_type_safe(item)
if not obj_type then
return '{NoHelp LuaObject}'
end
if obj_type == 'LuaPlayer' then
luaPlayer[2] = item.name or 'nil'
luaPlayer[4] = item.index or 'nil'
return concat(luaPlayer)
elseif obj_type == 'LuaEntity' then
luaEntity[2] = item.name or 'nil'
luaEntity[4] = item.unit_number or 'nil'
return concat(luaEntity)
elseif obj_type == 'LuaGuiElement' then
local name = item.name
luaGuiElement[2] = gui_names and gui_names[name] or name or 'nil'
return concat(luaGuiElement)
else
luaObject[2] = obj_type
luaObject[4] = get_name_safe(item)
return concat(luaObject)
end
end
local inspect_options = {process = inspect_process}
function Public.dump(data)
return inspect(data, inspect_options)
end
local dump = Public.dump
function Public.dump_ignore_builder(ignore)
local function process(item)
if ignore[item] then
return nil
end
return inspect_process(item)
end
local options = {process = process}
return function(data)
return inspect(data, options)
end
end
function Public.dump_function(func)
local res = {'upvalues:\n'}
local i = 1
while true do
local n, v = debug.getupvalue(func, i)
if n == nil then
break
elseif n ~= '_ENV' then
res[#res + 1] = n
res[#res + 1] = ' = '
res[#res + 1] = dump(v)
res[#res + 1] = '\n'
end
i = i + 1
end
return concat(res)
end
function Public.dump_text(text, player)
local func = loadstring('return ' .. text)
if not func then
return false
end
rawset(game, 'player', player)
local suc, var = pcall(func)
rawset(game, 'player', nil)
if not suc then
return false
end
return true, dump(var)
end
return Public

View File

@@ -0,0 +1,161 @@
local Gui = require 'utils.gui' --- @dep utils.gui
local Color = require 'utils.color_presets' --- @dep utils.color_presets
local Model = require 'modules.gui.debug.model' --- @dep modules.gui.debug.model
local dump_function = Model.dump_function
local loaded = _G.package.loaded
local Public = {}
local ignore = {
_G = true,
package = true,
coroutine = true,
table = true,
string = true,
bit32 = true,
math = true,
debug = true,
serpent = true,
['overrides.math'] = true,
util = true,
['overrides.inspect'] = true,
['mod-gui'] = true
}
local file_label_name = Gui.uid_name()
local left_panel_name = Gui.uid_name()
local breadcrumbs_name = Gui.uid_name()
local top_panel_name = Gui.uid_name()
local variable_label_name = Gui.uid_name()
local text_box_name = Gui.uid_name()
Public.name = 'package'
function Public.show(container)
local main_flow = container.add {type = 'flow', direction = 'horizontal'}
local left_panel = main_flow.add {type = 'scroll-pane', name = left_panel_name}
local left_panel_style = left_panel.style
left_panel_style.width = 300
for name, file in pairs(loaded) do
if not ignore[name] then
local file_label =
left_panel.add({type = 'flow'}).add {type = 'label', name = file_label_name, caption = name}
Gui.set_data(file_label, file)
end
end
local right_flow = main_flow.add {type = 'flow', direction = 'vertical'}
local breadcrumbs = right_flow.add {type = 'label', name = breadcrumbs_name}
local top_panel = right_flow.add {type = 'scroll-pane', name = top_panel_name}
local top_panel_style = top_panel.style
top_panel_style.height = 200
top_panel_style.maximal_width = 1000
top_panel_style.horizontally_stretchable = true
local text_box = right_flow.add {type = 'text-box', name = text_box_name}
text_box.read_only = true
text_box.selectable = true
local text_box_style = text_box.style
text_box_style.vertically_stretchable = true
text_box_style.horizontally_stretchable = true
text_box_style.maximal_width = 1000
text_box_style.maximal_height = 1000
local data = {
left_panel = left_panel,
breadcrumbs = breadcrumbs,
top_panel = top_panel,
text_box = text_box,
selected_file_label = nil,
selected_variable_label = nil
}
Gui.set_data(left_panel, data)
Gui.set_data(top_panel, data)
end
Gui.on_click(
file_label_name,
function(event)
local element = event.element
local file = Gui.get_data(element)
local left_panel = element.parent.parent
local data = Gui.get_data(left_panel)
local selected_file_label = data.selected_file_label
if selected_file_label then
selected_file_label.style.font_color = Color.white
end
element.style.font_color = Color.orange
data.selected_file_label = element
local top_panel = data.top_panel
local text_box = data.text_box
Gui.clear(top_panel)
local file_type = type(file)
if file_type == 'table' then
for k, v in pairs(file) do
local label =
top_panel.add({type = 'flow'}).add {type = 'label', name = variable_label_name, caption = k}
Gui.set_data(label, v)
end
elseif file_type == 'function' then
text_box.text = dump_function(file)
else
text_box.text = tostring(file)
end
end
)
Gui.on_click(
variable_label_name,
function(event)
local element = event.element
local variable = Gui.get_data(element)
local top_panel = element.parent.parent
local data = Gui.get_data(top_panel)
local text_box = data.text_box
local variable_type = type(variable)
if variable_type == 'table' then
Gui.clear(top_panel)
for k, v in pairs(variable) do
local label =
top_panel.add({type = 'flow'}).add {type = 'label', name = variable_label_name, caption = k}
Gui.set_data(label, v)
end
return
end
local selected_label = data.selected_variable_label
if selected_label and selected_label.valid then
selected_label.style.font_color = Color.white
end
element.style.font_color = Color.orange
data.selected_variable_label = element
if variable_type == 'function' then
text_box.text = dump_function(variable)
else
text_box.text = tostring(variable)
end
end
)
return Public

View File

@@ -0,0 +1,129 @@
local Gui = require 'utils.gui' --- @dep utils.gui
local Global = require 'utils.global' --- @dep utils.global
local Token = require 'utils.token' --- @dep utils.token
local Color = require 'utils.color_presets' --- @dep utils.color_presets
local Model = require 'modules.gui.debug.model' --- @dep modules.gui.debug.model
local dump = Model.dump
local dump_text = Model.dump_text
local concat = table.concat
local Public = {}
local header_name = Gui.uid_name()
local left_panel_name = Gui.uid_name()
local right_panel_name = Gui.uid_name()
local input_text_box_name = Gui.uid_name()
local refresh_name = Gui.uid_name()
Public.name = 'Global'
function Public.show(container)
local main_flow = container.add {type = 'flow', direction = 'horizontal'}
local left_panel = main_flow.add {type = 'scroll-pane', name = left_panel_name}
local left_panel_style = left_panel.style
left_panel_style.width = 300
for token_id, token_name in pairs(Global.names) do
local header = left_panel.add({type = 'flow'}).add {type = 'label', name = header_name, caption = token_name}
Gui.set_data(header, token_id)
end
local right_flow = main_flow.add {type = 'flow', direction = 'vertical'}
local right_top_flow = right_flow.add {type = 'flow', direction = 'horizontal'}
local input_text_box = right_top_flow.add {type = 'text-box', name = input_text_box_name}
local input_text_box_style = input_text_box.style
input_text_box_style.horizontally_stretchable = true
input_text_box_style.height = 32
input_text_box_style.maximal_width = 1000
local refresh_button =
right_top_flow.add {type = 'sprite-button', name = refresh_name, sprite = 'utility/reset', tooltip = 'refresh'}
local refresh_button_style = refresh_button.style
refresh_button_style.width = 32
refresh_button_style.height = 32
local right_panel = right_flow.add {type = 'text-box', name = right_panel_name}
right_panel.read_only = true
right_panel.selectable = true
local right_panel_style = right_panel.style
right_panel_style.vertically_stretchable = true
right_panel_style.horizontally_stretchable = true
right_panel_style.maximal_width = 1000
right_panel_style.maximal_height = 1000
local data = {
right_panel = right_panel,
input_text_box = input_text_box,
selected_header = nil
}
Gui.set_data(input_text_box, data)
Gui.set_data(left_panel, data)
Gui.set_data(refresh_button, data)
end
Gui.on_click(
header_name,
function(event)
local element = event.element
local token_id = Gui.get_data(element)
local left_panel = element.parent.parent
local data = Gui.get_data(left_panel)
local right_panel = data.right_panel
local selected_header = data.selected_header
local input_text_box = data.input_text_box
if selected_header then
selected_header.style.font_color = Color.white
end
element.style.font_color = Color.orange
data.selected_header = element
input_text_box.text = concat {'global.tokens[', token_id, ']'}
input_text_box.style.font_color = Color.black
local content = dump(Token.get_global(token_id)) or 'nil'
right_panel.text = content
end
)
local function update_dump(text_input, data, player)
local suc, ouput = dump_text(text_input.text, player)
if not suc then
text_input.style.font_color = Color.red
else
text_input.style.font_color = Color.black
data.right_panel.text = ouput
end
end
Gui.on_text_changed(
input_text_box_name,
function(event)
local element = event.element
local data = Gui.get_data(element)
update_dump(element, data, event.player)
end
)
Gui.on_click(
refresh_name,
function(event)
local element = event.element
local data = Gui.get_data(element)
local input_text_box = data.input_text_box
update_dump(input_text_box, data, event.player)
end
)
return Public

View File

@@ -0,0 +1,185 @@
--[[-- Gui Module - Landfill
- Landfill blueprint
@gui Landfill
@alias landfill_container
]]
local Gui = require 'expcore.gui' --- @dep expcore.gui
local Event = require 'utils.event' --- @dep utils.event
local Roles = require 'expcore.roles' --- @dep expcore.roles
local rolling_stocks = {}
local function landfill_init()
for name, _ in pairs(game.get_filtered_entity_prototypes({{filter = 'rolling-stock'}})) do
rolling_stocks[name] = true
end
end
local function rotate_bounding_box(box)
return {
left_top = {
x = -box.right_bottom.y,
y = box.left_top.x
},
right_bottom = {
x = -box.left_top.y,
y = box.right_bottom.x
}
}
end
local function curve_flip_lr(oc)
local nc = table.deepcopy(oc)
for r=1, 8 do
for c=1, 8 do
nc[r][c] = oc[r][9 - c]
end
end
return nc
end
local function curve_flip_d(oc)
local nc = table.deepcopy(oc)
for r=1, 8 do
for c=1, 8 do
nc[r][c] = oc[c][r]
end
end
return nc
end
local curves = {}
curves[1] = {
{0, 0, 0, 0, 0, 1, 0, 0},
{0, 0, 0, 0, 1, 1, 1, 0},
{0, 0, 0, 1, 1, 1, 1, 0},
{0, 0, 0, 1, 1, 1, 0, 0},
{0, 0, 1, 1, 1, 0, 0, 0},
{0, 0, 1, 1, 1, 0, 0, 0},
{0, 0, 1, 1, 0, 0, 0, 0},
{0, 0, 1, 1, 0, 0, 0, 0}
}
curves[6] = curve_flip_d(curves[1])
curves[3] = curve_flip_lr(curves[6])
curves[4] = curve_flip_d(curves[3])
curves[5] = curve_flip_lr(curves[4])
curves[2] = curve_flip_d(curves[5])
curves[7] = curve_flip_lr(curves[2])
curves[8] = curve_flip_d(curves[7])
local curve_n = {}
for i, map in ipairs(curves) do
curve_n[i] = {}
local index = 1
for r=1, 8 do
for c=1, 8 do
if map[r][c] == 1 then
curve_n[i][index] = {
['x'] = c - 5,
['y'] = r - 5
}
index = index + 1
end
end
end
end
local function landfill_gui_add_landfill(blueprint)
local entities = blueprint.get_blueprint_entities()
local tile_index = 0
local new_tiles = {}
for _, ent in pairs(entities) do
-- vehicle
if not (rolling_stocks[ent.name] or ent.name == 'offshore-pump') then
-- curved rail, special
if ent.name ~= 'curved-rail' then
local box = game.entity_prototypes[ent.name].collision_box or game.entity_prototypes[ent.name].selection_box
if game.entity_prototypes[ent.name].collision_mask['ground-tile'] == nil then
if ent.direction then
if ent.direction ~= defines.direction.north then
box = rotate_bounding_box(box)
if ent.direction ~= defines.direction.east then
box = rotate_bounding_box(box)
if ent.direction ~= defines.direction.south then
box = rotate_bounding_box(box)
end
end
end
end
for y = math.floor(ent.position.y + box.left_top.y), math.floor(ent.position.y + box.right_bottom.y), 1 do
for x = math.floor(ent.position.x + box.left_top.x), math.floor(ent.position.x + box.right_bottom.x), 1 do
tile_index = tile_index + 1
new_tiles[tile_index] = {
name = 'landfill',
position = {x, y}
}
end
end
end
-- curved rail
else
local curve_mask = curve_n[ent.direction or 8]
for m=1, #curve_mask do
new_tiles[tile_index + 1] = {
name = 'landfill',
position = {curve_mask[m].x + ent.position.x, curve_mask[m].y + ent.position.y}
}
tile_index = tile_index + 1
end
end
end
end
local old_tiles = blueprint.get_blueprint_tiles()
if old_tiles then
for _, old_tile in pairs(old_tiles) do
new_tiles[tile_index + 1] = {
name = 'landfill',
position = {old_tile.position.x, old_tile.position.y}
}
tile_index = tile_index + 1
end
end
return {tiles = new_tiles}
end
-- @element toolbar_button
Gui.toolbar_button('item/landfill', {'landfill.main-tooltip'}, function(player)
return Roles.player_allowed(player, 'gui/landfill')
end)
:on_click(function(player, _, _)
if player.cursor_stack and player.cursor_stack.valid_for_read then
if player.cursor_stack.type == 'blueprint' and player.cursor_stack.is_blueprint_setup() then
local modified = landfill_gui_add_landfill(player.cursor_stack)
if modified and next(modified.tiles) then
player.cursor_stack.set_blueprint_tiles(modified.tiles)
end
end
else
player.print{'landfill.cursor-none'}
end
end)
Event.add(defines.events.on_player_joined_game, landfill_init)

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,315 @@
---- module inserter
-- @gui Module
local Gui = require 'expcore.gui' --- @dep expcore.gui
local Event = require 'utils.event' --- @dep utils.event
local Roles = require 'expcore.roles' --- @dep expcore.roles
local config = require 'config.module' --- @dep config.module
local Selection = require 'modules.control.selection' --- @dep modules.control.selection
local SelectionModuleArea = 'ModuleArea'
--- align an aabb to the grid by expanding it
local function aabb_align_expand(aabb)
return {
left_top = {
x = math.floor(aabb.left_top.x),
y = math.floor(aabb.left_top.y)
},
right_bottom = {
x = math.ceil(aabb.right_bottom.x),
y = math.ceil(aabb.right_bottom.y)
}
}
end
local module_container
local machine_name = {}
for k, _ in pairs(config.machine) do
table.insert(machine_name, k)
end
local prod_module_names = {}
local function get_module_name()
for name, item in pairs(game.item_prototypes) do
if item.module_effects and item.module_effects.productivity and item.module_effects.productivity.bonus > 0 then
prod_module_names[#prod_module_names + 1] = name
end
end
end
local elem_filter = {
name = {{
filter = 'name',
name = machine_name
}},
normal = {{
filter = 'type',
type = 'module'
}, {
filter = 'name',
name = prod_module_names,
mode = 'and',
invert = true
}},
prod = {{
filter = 'type',
type = 'module'
}}
}
local function clear_module(player, area, machine)
for _, entity in pairs(player.surface.find_entities_filtered{area=area, name=machine, force=player.force}) do
for _, r in pairs(player.surface.find_entities_filtered{position=entity.position, name='item-request-proxy', force=player.force}) do
if r then
r.destroy{raise_destroy=true}
end
end
local m_current_module = entity.get_module_inventory()
if m_current_module then
local m_current_module_content = m_current_module.get_contents()
if m_current_module_content then
for k, m in pairs(m_current_module_content) do
player.surface.spill_item_stack(entity.bounding_box.left_top, {name=k, count=m}, true, player.force, false)
end
end
m_current_module.clear()
end
end
end
local function apply_module(player, area, machine, modules)
for _, entity in pairs(player.surface.find_entities_filtered{area=area, name=machine, force=player.force}) do
local m_current_recipe
if entity.prototype.crafting_speed then
m_current_recipe= entity.get_recipe()
end
if m_current_recipe then
if config.module_allowed[m_current_recipe.name] then
entity.surface.create_entity{name='item-request-proxy', target=entity, position=entity.position, force=entity.force, modules=modules['n']}
entity.last_user = player
else
entity.surface.create_entity{name='item-request-proxy', target=entity, position=entity.position, force=entity.force, modules=modules['p']}
entity.last_user = player
end
else
entity.surface.create_entity{name='item-request-proxy', target=entity, position=entity.position, force=entity.force, modules=modules['n']}
entity.last_user = player
end
end
end
--- when an area is selected to add protection to the area
Selection.on_selection(SelectionModuleArea, function(event)
local area = aabb_align_expand(event.area)
local player = game.get_player(event.player_index)
local frame = Gui.get_left_element(player, module_container)
local scroll_table = frame.container.scroll.table
for i=1, config.default_module_row_count do
local mma = scroll_table['module_mm_' .. i .. '_0'].elem_value
if mma then
local mm = {
['n'] = {},
['p'] = {}
}
for j=1, game.entity_prototypes[mma].module_inventory_size, 1 do
local mmo = scroll_table['module_mm_' .. i .. '_' .. j].elem_value
if mmo then
if mm['n'][mmo] then
mm['n'][mmo] = mm['n'][mmo] + 1
mm['p'][mmo] = mm['p'][mmo] + 1
else
mm['n'][mmo] = 1
mm['p'][mmo] = 1
end
end
end
for k, v in pairs(mm['p']) do
if k:find('productivity') then
local module_name = k:gsub('productivity', 'effectivity')
mm['p'][module_name] = (mm['p'][module_name] or 0) + v
mm['p'][k] = nil
end
end
if mm then
clear_module(player, area, mma)
apply_module(player, area, mma, mm)
end
end
end
end)
local function row_set(player, element)
local frame = Gui.get_left_element(player, module_container)
local scroll_table = frame.container.scroll.table
if scroll_table[element .. '0'].elem_value then
for i=1, config.module_slot_max do
if i <= game.entity_prototypes[scroll_table[element .. '0'].elem_value].module_inventory_size then
if config.machine[scroll_table[element .. '0'].elem_value].prod then
scroll_table[element .. i].elem_filters = elem_filter.prod
else
scroll_table[element .. i].elem_filters = elem_filter.normal
end
scroll_table[element .. i].enabled = true
scroll_table[element .. i].elem_value = config.machine[scroll_table[element .. '0'].elem_value].module
else
scroll_table[element .. i].enabled = false
scroll_table[element .. i].elem_value = nil
end
end
else
local mf = elem_filter.normal
for i=1, config.module_slot_max do
scroll_table[element .. i].enabled = false
scroll_table[element .. i].elem_filters = mf
scroll_table[element .. i].elem_value = nil
end
end
end
local button_apply =
Gui.element{
type = 'button',
caption = 'Apply',
style = 'button'
}:on_click(function(player)
if Selection.is_selecting(player, SelectionModuleArea) then
Selection.stop(player)
else
Selection.start(player, SelectionModuleArea)
end
end)
module_container =
Gui.element(function(definition, parent)
local container = Gui.container(parent, definition.name, (config.module_slot_max + 2) * 36)
Gui.header(container, 'Module Inserter', '', true)
local scroll_table = Gui.scroll_table(container, (config.module_slot_max + 2) * 36, config.module_slot_max + 1)
for i=1, config.default_module_row_count do
scroll_table.add{
name = 'module_mm_' .. i .. '_0',
type = 'choose-elem-button',
elem_type = 'entity',
elem_filters = elem_filter.name,
style = 'slot_button'
}
for j=1, config.module_slot_max do
scroll_table.add{
name = 'module_mm_' .. i .. '_' .. j,
type = 'choose-elem-button',
elem_type = 'item',
elem_filters = elem_filter.normal,
style = 'slot_button',
enabled = false
}
end
end
button_apply(container)
return container.parent
end)
:static_name(Gui.unique_static_name)
:add_to_left_flow()
Gui.left_toolbar_button('item/productivity-module-3', {'module.main-tooltip'}, module_container, function(player)
return Roles.player_allowed(player, 'gui/module')
end)
Event.add(defines.events.on_gui_elem_changed, function(event)
if event.element.name:sub(1, 10) == 'module_mm_' then
if event.element.name:sub(-1) == '0' then
row_set(game.players[event.player_index], 'module_mm_' .. event.element.name:sub(-3):sub(1, 1) .. '_')
end
end
end)
Event.add(defines.events.on_player_joined_game, get_module_name)
Event.add(defines.events.on_entity_settings_pasted, function(event)
local source = event.source
local destination = event.destination
local player = game.players[event.player_index]
if not player then
return
end
if not source or not source.valid then
return
end
if not destination or not destination.valid then
return
end
-- rotate machine also
if config.copy_paste_rotation then
if (source.name == destination.name or source.prototype.fast_replaceable_group == destination.prototype.fast_replaceable_group) then
if source.supports_direction and destination.supports_direction and source.type ~= 'transport-belt' then
local destination_box = destination.bounding_box
local ltx = destination_box.left_top.x
local lty = destination_box.left_top.y
local rbx = destination_box.right_bottom.x
local rby = destination_box.right_bottom.y
local old_direction = destination.direction
destination.direction = source.direction
if ltx ~= destination_box.left_top.x or lty ~= destination_box.left_top.y or rbx ~= destination_box.right_bottom.x or rby ~= destination_box.right_bottom.y then
destination.direction = old_direction
end
end
end
end
if config.copy_paste_module then
if source.name ~= destination.name then
return
end
local source_inventory = source.get_module_inventory()
if not source_inventory then
return
end
local source_inventory_content = source_inventory.get_contents()
if not source_inventory_content then
return
end
clear_module(player, destination.bounding_box, destination.name)
if next(source_inventory_content) ~= nil then
apply_module(player, destination.bounding_box, destination.name, {['n']=source_inventory_content, ['p']=source_inventory_content})
end
end
end)

View File

@@ -0,0 +1,434 @@
--[[-- Gui Module - Player List
- Adds a player list to show names and play time; also includes action buttons which can preform actions to players
@gui Player-List
@alias player_list
]]
-- luacheck:ignore 211/Colors
local Gui = require 'expcore.gui' --- @dep expcore.gui
local Roles = require 'expcore.roles' --- @dep expcore.roles
local Datastore = require 'expcore.datastore' --- @dep expcore.datastore
local Event = require 'utils.event' --- @dep utils.event
local config = require 'config.gui.player_list_actions' --- @dep config.gui.player_list_actions
local Colors = require 'utils.color_presets' --- @dep utils.color_presets
local format_time = _C.format_time --- @dep expcore.common
--- Stores all data for the warp gui
local PlayerListData = Datastore.connect('PlayerListData')
PlayerListData:set_serializer(Datastore.name_serializer)
local SelectedPlayer = PlayerListData:combine('SelectedPlayer')
local SelectedAction = PlayerListData:combine('SelectedAction')
-- Set the config to use these stores
config.set_datastores(SelectedPlayer, SelectedAction)
--- Button used to open the action bar
-- @element open_action_bar
local open_action_bar =
Gui.element{
type = 'sprite-button',
sprite = 'utility/expand_dots_white',
tooltip = {'player-list.open-action-bar'},
style = 'frame_button',
name = Gui.unique_static_name
}
:style{
padding = -2,
width = 8,
height = 14
}
:on_click(function(player, element, _)
local selected_player_name = element.parent.name
local old_selected_player_name = SelectedPlayer:get(player)
if selected_player_name == old_selected_player_name then
SelectedPlayer:remove(player)
else
SelectedPlayer:set(player, selected_player_name)
end
end)
--- Button used to close the action bar
-- @element close_action_bar
local close_action_bar =
Gui.element{
type = 'sprite-button',
sprite = 'utility/close_black',
tooltip = {'player-list.close-action-bar'},
style = 'slot_sized_button_red'
}
:style(Gui.sprite_style(30, -1, { top_margin = -1, right_margin = -1 }))
:on_click(function(player, _)
SelectedPlayer:remove(player)
SelectedAction:remove(player)
end)
--- Button used to confirm a reason
-- @element reason_confirm
local reason_confirm =
Gui.element{
type = 'sprite-button',
sprite = 'utility/confirm_slot',
tooltip = {'player-list.reason-confirm'},
style = 'slot_sized_button_green'
}
:style(Gui.sprite_style(30, -1, { left_margin = -2, right_margin = -1 }))
:on_click(function(player, element)
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)
element.parent.entry.text = ''
end)
--- Set of elements that are used to make up a row of the player table
-- @element add_player_base
local add_player_base =
Gui.element(function(_, parent, player_data)
-- Add the button to open the action bar
local toggle_action_bar_flow = parent.add{ type = 'flow', name = player_data.name }
open_action_bar(toggle_action_bar_flow)
-- Add the player name
local player_name = parent.add{
type = 'label',
name = 'player-name-'..player_data.index,
caption = player_data.name,
tooltip = {'player-list.open-map', player_data.name, player_data.tag, player_data.role_name}
}
player_name.style.padding = {0, 2,0, 0}
player_name.style.font_color = player_data.chat_color
-- Add the time played label
local alignment = Gui.alignment(parent, 'player-time-'..player_data.index)
local time_label = alignment.add{
name = 'label',
type = 'label',
caption = player_data.caption,
tooltip = player_data.tooltip
}
time_label.style.padding = 0
return player_name
end)
:on_click(function(player, element, event)
local selected_player_name = element.caption
local selected_player = game.players[selected_player_name]
if event.button == defines.mouse_button_type.left then
-- LMB will open the map to the selected player
local position = selected_player.position
event.player.zoom_to_world(position, 1.75)
else
-- RMB will toggle the settings
local old_selected_player_name = SelectedPlayer:get(player)
if selected_player_name == old_selected_player_name then
SelectedPlayer:remove(player)
SelectedAction:remove(player)
else
SelectedPlayer:set(player, selected_player_name)
end
end
end)
-- Removes the three elements that are added as part of the base
local function remove_player_base(parent, player)
Gui.destroy_if_valid(parent[player.name])
Gui.destroy_if_valid(parent['player-name-'..player.index])
Gui.destroy_if_valid(parent['player-time-'..player.index])
end
-- Update the time label for a player using there player time data
local function update_player_base(parent, player_time)
local time_element = parent[player_time.element_name]
if time_element and time_element.valid then
time_element.label.caption = player_time.caption
time_element.label.tooltip = player_time.tooltip
end
end
--- Adds all the buttons and flows that make up the action bar
-- @element add_action_bar
local add_action_bar_buttons =
Gui.element(function(_, parent)
close_action_bar(parent)
-- Loop over all the buttons in the config
for action_name, button_data in pairs(config.buttons) do
-- Added the permission flow
local permission_flow = parent.add{ type = 'flow', name = action_name }
permission_flow.visible = false
-- Add the buttons under that permission
for _, button in ipairs(button_data) do
button(permission_flow)
end
end
return parent
end)
--- Updates the visible state of the action bar buttons
local function update_action_bar(element)
local player = Gui.get_player_from_element(element)
local selected_player_name = SelectedPlayer:get(player)
if not selected_player_name then
-- Hide the action bar when no player is selected
element.visible = false
else
local selected_player = game.players[selected_player_name]
if not selected_player.connected then
-- If the player is offline then reest stores
element.visible = false
SelectedPlayer:remove(player)
SelectedAction:remove(player)
else
-- Otherwise check what actions the player is allowed to use
element.visible = true
for action_name, buttons in pairs(config.buttons) do
if buttons.auth and not buttons.auth(player, selected_player) then
element[action_name].visible = false
elseif Roles.player_allowed(player, action_name) then
element[action_name].visible = true
end
end
end
end
end
--- Main player list container for the left flow
-- @element player_list_container
local player_list_container =
Gui.element(function(definition, parent)
-- Draw the internal container
local container = Gui.container(parent, definition.name, 200)
-- Draw the scroll table for the players
local scroll_table = Gui.scroll_table(container, 184, 3)
-- Change the style of the scroll table
local scroll_table_style = scroll_table.style
scroll_table_style.padding = {1, 0,1, 2}
-- Add the action bar
local action_bar = Gui.footer(container, nil, nil, false, 'action_bar')
-- Change the style of the action bar
local action_bar_style = action_bar.style
action_bar_style.height = 35
action_bar_style.padding = {1, 3}
action_bar.visible = false
-- Add the buttons to the action bar
add_action_bar_buttons(action_bar)
-- Add the reason bar
local reason_bar = Gui.footer(container, nil, nil, false, 'reason_bar')
-- Change the style of the reason bar
local reason_bar_style = reason_bar.style
reason_bar_style.height = 35
reason_bar_style.padding = {-1, 3}
reason_bar.visible = false
-- Add the text entry for the reason bar
local reason_field =
reason_bar.add{
name = 'entry',
type = 'textfield',
style = 'stretchable_textfield',
tooltip = {'player-list.reason-entry'}
}
-- Change the style of the text entry
local reason_entry_style = reason_field.style
reason_entry_style.padding = 0
reason_entry_style.height = 28
reason_entry_style.minimal_width = 160
-- Add the confirm reason button
reason_confirm(reason_bar)
-- Return the exteral container
return container.parent
end)
:static_name(Gui.unique_static_name)
:add_to_left_flow(true)
--- Button on the top flow used to toggle the player list container
-- @element toggle_player_list
Gui.left_toolbar_button('entity/character', {'player-list.main-tooltip'}, player_list_container, function(player)
return Roles.player_allowed(player, 'gui/player-list')
end)
-- Get caption and tooltip format for a player
local function get_time_formats(online_time, afk_time)
local tick = game.tick > 0 and game.tick or 1
local percent = math.round(online_time/tick, 3)*100
local caption = format_time(online_time)
local tooltip = {'player-list.afk-time', percent, format_time(afk_time, {minutes=true, long=true})}
return caption, tooltip
end
-- Get the player time to be used to update time label
local function get_player_times()
local ctn = 0
local player_times = {}
for _, player in pairs(game.connected_players) do
ctn = ctn + 1
-- Add the player time details to the array
local caption, tooltip = get_time_formats(player.online_time, player.afk_time)
player_times[ctn] = {
element_name = 'player-time-'..player.index,
caption = caption,
tooltip = tooltip
}
end
return player_times
end
-- Get a sorted list of all online players
local function get_player_list_order()
-- Sort all the online players into roles
local players = {}
for _, player in pairs(game.connected_players) do
local highest_role = Roles.get_player_highest_role(player)
if not players[highest_role.name] then
players[highest_role.name] = {}
end
table.insert(players[highest_role.name], player)
end
-- Sort the players from roles into a set order
local ctn = 0
local player_list_order = {}
for _, role_name in pairs(Roles.config.order) do
if players[role_name] then
for _, player in pairs(players[role_name]) do
ctn = ctn + 1
-- Add the player data to the array
local caption, tooltip = get_time_formats(player.online_time, player.afk_time)
player_list_order[ctn] = {
name = player.name,
index = player.index,
tag = player.tag,
role_name = role_name,
chat_color = player.chat_color,
caption = caption,
tooltip = tooltip
}
end
end
end
--[[Adds fake players to the player list
local tick = game.tick+1
for i = 1, 10 do
local online_time = math.random(1, tick)
local afk_time = math.random(online_time-(tick/10), tick)
local caption, tooltip = get_time_formats(online_time, afk_time)
player_list_order[ctn+i] = {
name='Player '..i,
index=0-i,
tag='',
role_name = 'Fake Player',
chat_color = table.get_random_dictionary_entry(Colors),
caption = caption,
tooltip = tooltip
}
end--]]
return player_list_order
end
--- Update the play times every 30 sections
Event.on_nth_tick(1800, function()
local player_times = get_player_times()
for _, player in pairs(game.connected_players) do
local frame = Gui.get_left_element(player, player_list_container)
local scroll_table = frame.container.scroll.table
for _, player_time in pairs(player_times) do
update_player_base(scroll_table, player_time)
end
end
end)
--- When a player leaves only remove they entry
Event.add(defines.events.on_player_left_game, function(event)
local remove_player = game.players[event.player_index]
for _, player in pairs(game.connected_players) do
local frame = Gui.get_left_element(player, player_list_container)
local scroll_table = frame.container.scroll.table
remove_player_base(scroll_table, remove_player)
local selected_player_name = SelectedPlayer:get(player)
if selected_player_name == remove_player.name then
SelectedPlayer:remove(player)
SelectedAction:remove(player)
end
end
end)
--- All other events require a full redraw of the table
local function redraw_player_list()
local player_list_order = get_player_list_order()
for _, player in pairs(game.connected_players) do
local frame = Gui.get_left_element(player, player_list_container)
local scroll_table = frame.container.scroll.table
scroll_table.clear()
for _, next_player_data in ipairs(player_list_order) do
add_player_base(scroll_table, next_player_data)
end
end
end
Event.add(defines.events.on_player_joined_game, redraw_player_list)
Event.add(Roles.events.on_role_assigned, redraw_player_list)
Event.add(Roles.events.on_role_unassigned, redraw_player_list)
--- When the action player is changed the action bar will update
SelectedPlayer:on_update(function(player_name, selected_player)
local player = game.players[player_name]
local frame = Gui.get_left_element(player, player_list_container)
local scroll_table = frame.container.scroll.table
update_action_bar(frame.container.action_bar)
for _, next_player in pairs(game.connected_players) do
local element = scroll_table[next_player.name][open_action_bar.name]
local style = 'frame_button'
if next_player.name == selected_player then
style = 'tool_button'
end
element.style = style
local element_style = element.style
element_style.padding = -2
element_style.width = 8
element_style.height = 14
end
end)
--- When the action name is changed the reason input will update
SelectedAction:on_update(function(player_name, selected_action)
local player = game.players[player_name]
local frame = Gui.get_left_element(player, player_list_container)
local element = frame.container.reason_bar
if selected_action then
-- if there is a new value then check the player is still online
local selected_player_name = SelectedPlayer:get(player_name)
local selected_player = game.players[selected_player_name]
if selected_player.connected then
element.visible = true
else
-- Clear if the player is offline
SelectedPlayer:remove(player)
SelectedAction:remove(player)
end
else
element.visible = false
end
end)

View File

@@ -0,0 +1,219 @@
---- module pd
-- @gui PlayerData
local Gui = require 'expcore.gui' --- @dep expcore.gui
local Roles = require 'expcore.roles' --- @dep expcore.roles
local Event = require 'utils.event' --- @dep utils.event
local PlayerData = require 'expcore.player_data' --- @dep expcore.player_data
require 'modules.data.statistics'
local format_time = _C.format_time --- @dep expcore.common
local format_number = require('util').format_number --- @dep util
local pd_container
local label_width = {
['name'] = 135,
['count'] = 105,
['total'] = 480
}
local function format_time_short(value)
return format_time(value*3600, {
hours=true,
minutes=true,
seconds=false
})
end
local function format_number_n(n)
return format_number(math.floor(n)) .. string.format('%.2f', n % 1):sub(2)
end
local playerStats = PlayerData.Statistics
local computed_stats = {
DamageDeathRatio = {
default = format_number_n(0),
calculate = function(player_name)
return format_number_n(playerStats['DamageDealt']:get(player_name, 0) / playerStats['Deaths']:get(player_name, 1))
end
},
KillDeathRatio = {
default = format_number_n(0),
calculate = function(player_name)
return format_number_n(playerStats['Kills']:get(player_name, 0) / playerStats['Deaths']:get(player_name, 1))
end
},
SessionTime = {
default = format_time_short(0),
calculate = function(player_name)
return format_time_short((playerStats['Playtime']:get(player_name, 0) - playerStats['AfkTime']:get(player_name, 0)) / playerStats['JoinCount']:get(player_name, 1))
end
},
BuildRatio = {
default = format_number_n(0),
calculate = function(player_name)
return format_number_n(playerStats['MachinesBuilt']:get(player_name, 0) / playerStats['MachinesRemoved']:get(player_name, 1))
end
},
RocketPerHour = {
default = format_number_n(0),
calculate = function(player_name)
return format_number_n(playerStats['RocketsLaunched']:get(player_name, 0) * 60 / playerStats['Playtime']:get(player_name, 1))
end
},
TreeKillPerMinute = {
default = format_number_n(0),
calculate = function(player_name)
return format_number_n(playerStats['TreesDestroyed']:get(player_name, 0) / playerStats['Playtime']:get(player_name, 1))
end
},
NetPlayTime = {
default = format_time_short(0),
calculate = function(player_name)
return format_time_short((playerStats['Playtime']:get(player_name, 0) - playerStats['AfkTime']:get(player_name, 0)))
end
},
AFKTimeRatio = {
default = format_number_n(0),
calculate = function(player_name)
return format_number_n(playerStats['AfkTime']:get(player_name, 0) * 100 / playerStats['Playtime']:get(player_name, 1))
end
},
}
local label =
Gui.element(function(_, parent, width, caption, tooltip, name)
local new_label = parent.add{
type = 'label',
caption = caption,
tooltip = tooltip,
name = name,
style = 'heading_2_label'
}
new_label.style.width = width
return new_label
end)
local pd_data_set =
Gui.element(function(_, parent, name)
local pd_data_set = parent.add{type='flow', direction='vertical', name=name}
local disp = Gui.scroll_table(pd_data_set, label_width['total'], 4, 'disp')
for _, stat_name in pairs(PlayerData.Statistics.metadata.display_order) do
local child = PlayerData.Statistics[stat_name]
local metadata = child.metadata
local value = metadata.stringify_short and metadata.stringify_short(0) or metadata.stringify and metadata.stringify(0) or format_number(0)
label(disp, label_width['name'], metadata.name or {'exp-statistics.'..stat_name}, metadata.tooltip or {'exp-statistics.'..stat_name..'-tooltip'})
label(disp, label_width['count'], {'readme.data-format', value, metadata.unit or ''}, metadata.value_tooltip or {'exp-statistics.'..stat_name..'-tooltip'}, stat_name)
end
for stat_name, data in pairs(computed_stats) do
label(disp, label_width['name'], {'exp-statistics.'..stat_name}, {'exp-statistics.'..stat_name..'-tooltip'})
label(disp, label_width['count'], {'readme.data-format', data.default, ''}, {'exp-statistics.'..stat_name..'-tooltip'}, stat_name)
end
return pd_data_set
end)
local function pd_update(table, player_name)
for _, stat_name in pairs(PlayerData.Statistics.metadata.display_order) do
local child = PlayerData.Statistics[stat_name]
local metadata = child.metadata
local value = child:get(player_name)
if metadata.stringify_short then
value = metadata.stringify_short(value or 0)
elseif metadata.stringify then
value = metadata.stringify(value or 0)
else
value = format_number(value or 0)
end
table[stat_name].caption = {'readme.data-format', value, metadata.unit or ''}
end
for stat_name, data in pairs(computed_stats) do
table[stat_name].caption = {'readme.data-format', data.calculate(player_name), ''}
end
end
local pd_username_player =
Gui.element(function(definition, parent, player_list)
return parent.add{
name = definition.name,
type = 'drop-down',
items = player_list,
selected_index = #player_list > 0 and 1
}
end)
:style{
horizontally_stretchable = true
}:on_selection_changed(function(_, element, _)
local player_name = game.connected_players[element.selected_index]
local table = element.parent.parent.parent.parent['pd_st_2'].disp.table
pd_update(table, player_name)
end)
:static_name(Gui.unique_static_name)
local pd_username_update =
Gui.element{
type = 'button',
name = Gui.unique_static_name,
caption = 'update'
}:style{
width = 128
}:on_click(function(_, element, _)
local player_index = element.parent[pd_username_player.name].selected_index
if player_index > 0 then
local player_name = game.connected_players[player_index]
local table = element.parent.parent.parent.parent['pd_st_2'].disp.table
pd_update(table, player_name)
end
end)
local pd_username_set =
Gui.element(function(_, parent, name, player_list)
local pd_username_set = parent.add{type='flow', direction='vertical', name=name}
local disp = Gui.scroll_table(pd_username_set, label_width['total'], 2, 'disp')
pd_username_player(disp, player_list)
pd_username_update(disp)
return pd_username_set
end)
pd_container =
Gui.element(function(definition, parent)
local container = Gui.container(parent, definition.name, label_width['total'])
local player_list = {}
for _, player in pairs(game.connected_players) do
table.insert(player_list, player.name)
end
pd_username_set(container, 'pd_st_1', player_list)
pd_data_set(container, 'pd_st_2')
return container.parent
end)
:static_name(Gui.unique_static_name)
:add_to_left_flow()
Gui.left_toolbar_button('item/power-armor-mk2', 'Player Data GUI', pd_container, function(player)
return Roles.player_allowed(player, 'gui/playerdata')
end)
local function gui_player_list_update()
local player_list = {}
for _, player in pairs(game.connected_players) do
table.insert(player_list, player.name)
end
for _, player in pairs(game.connected_players) do
local frame = Gui.get_left_element(player, pd_container)
frame.container['pd_st_1'].disp.table[pd_username_player.name].items = player_list
end
end
Event.add(defines.events.on_player_joined_game, gui_player_list_update)
Event.add(defines.events.on_player_left_game, gui_player_list_update)

Some files were not shown because too many files have changed in this diff Show More