You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

948 lines
28 KiB

local util = {}
util.mod_prefix = "se-" -- update strings.cfg
util.min = math.min
util.max = math.max
util.floor = math.floor
util.abs = math.abs
util.sqrt = math.sqrt
util.sin = math.sin
util.cos = math.cos
util.atan = math.atan
util.atan2 = math.atan2
util.pi = math.pi
util.remove = table.remove
util.insert = table.insert
util.str_gsub = string.gsub
function string.starts(String,Start)
return string.sub(String,1,string.len(Start))==Start
end
function util.deep_copy (t)
return table.deepcopy(t)
end
function util.shallow_copy (t) -- shallow-copy a table
if type(t) ~= "table" then return t end
local meta = getmetatable(t)
local target = {}
for k, v in pairs(t) do target[k] = v end
setmetatable(target, meta)
return target
end
function util.remove_from_table(list, item)
local index = 0
for _,_item in ipairs(list) do
if item == _item then
index = _
break
end
end
if index > 0 then
util.remove(list, index)
end
end
function util.shuffle (tbl)
size = #tbl
for i = size, 1, -1 do
--local rand = 1 + math.floor(size * (math.random() - 0.0000001))
local rand = math.random(size)
tbl[i], tbl[rand] = tbl[rand], tbl[i]
end
return tbl
end
function util.random_from_array (tbl)
--return tbl[1 + math.floor(#tbl * (math.random() - 0.0000001))]
return tbl[math.random(#tbl)]
end
function util.transfer_burner (entity_a, entity_b)
if entity_a.burner and entity_a.burner.currently_burning and entity_b.burner then
entity_b.burner.currently_burning = entity_a.burner.currently_burning.name
entity_b.burner.remaining_burning_fuel = entity_a.burner.remaining_burning_fuel
end
end
function util.copy_inventory (inv_a, inv_b, probability)
if not probability then probability = 1 end
if inv_a and inv_b then
local contents = inv_a.get_contents()
for item_type, item_count in pairs(contents) do
if probability == 1 or probability > math.random() then
inv_b.insert({name=item_type, count=item_count})
end
end
end
end
function util.move_inventory_items (inv_a, inv_b)
-- move all items from inv_a to inv_b
-- preserves item data but inv_b MUST be able to accept the items or they are lost.
-- inventory A is cleared.
for i = 1, util.min(#inv_a, #inv_b) do
if inv_a[i] and inv_a[i].valid then
inv_b.insert(inv_a[i])
end
end
inv_a.clear()
end
function util.transfer_inventory_filters_direct(inv_a, inv_b)
if inv_a.supports_filters() and inv_b.supports_filters() then
for i = 1, util.min(#inv_a, #inv_b) do
local filter = inv_a.get_filter(i)
if filter then
inv_b.set_filter(i, filter)
end
end
end
end
function util.transfer_inventory_filters (entity_a, entity_b, inventory_type)
local inv_a = entity_a.get_inventory(inventory_type)
local inv_b = entity_b.get_inventory(inventory_type)
util.transfer_inventory_filters_direct(inv_a, inv_b)
end
function util.transfer_equipment_grid (entity_a, entity_b) -- NOTE: entity can be an item
if not (entity_a.grid and entity_b.grid) then return end
local grid_a = entity_a.grid
local grid_b = entity_b.grid
local equipment = grid_a.equipment
for _, item in pairs(equipment) do
local new_item = grid_b.put({
name=item.name,
position=item.position})
if new_item then
if item.shield and item.shield > 0 then
new_item.shield = item.shield
end
if item.energy and item.energy > 0 then
new_item.energy = item.energy
end
else
util.send_message("Error transfering "..item.name)
end
end
end
function util.area_contains_position(area, position)
local px = position.x or position[1]
local py = position.y or position[2]
for k1, v1 in pairs(area) do
if k1 == 1 or k1 == "left_top" then
for k2, v2 in pairs(v1) do
if k2 == 1 or k2 == "x" then
if px < v2 then return false end
elseif k2 == 2 or k2 == "y" then
if py < v2 then return false end
end
end
elseif k1 == 2 or k1 == "right_bottom" then
for k2, v2 in pairs(v1) do
if k2 == 1 or k2 == "x" then
if px > v2 then return false end
elseif k2 == 2 or k2 == "y" then
if py > v2 then return false end
end
end
end
end
return true
end
function util.area_add_position(area, position)
local area2 = table.deepcopy(area)
for k1, v1 in pairs(area2) do
for k2, v2 in pairs(v1) do
if k2 == 1 or k2 == "x" then
v1[k2] = v2 + (position.x or position[1])
elseif k2 == 2 or k2 == "y" then
v1[k2] = v2 + (position.y or position[2])
end
end
end
return area2
end
function util.area_extend(area, range)
local area2 = table.deepcopy(area)
for k1, v1 in pairs(area2) do
local m = 1
if k1 == 1 or k1 == "left_top" then
m = -1
end
for k2, v2 in pairs(v1) do
v1[k2] = v2 + range * m
end
end
return area2
end
function util.position_to_area(position, radius)
return {{x = position.x - radius, y = position.y - radius},
{x = position.x + radius, y = position.y + radius}}
end
function util.position_to_tile(position)
return {x = math.floor(position.x or position[1]), y = math.floor(position.y or position[2])}
end
function util.tile_to_position(tile_position)
return {x = math.floor(tile_position.x)+0.5, y = math.floor(tile_position.y)+0.5}
end
function util.tile_to_area(tile_position, margin)
tile_position = {x = math.floor(tile_position.x or tile_position[1]), y = math.floor(tile_position.y or tile_position[2])}
margin = margin or 0.01
return {
{tile_position.x+margin, tile_position.y+margin},
{tile_position.x+1-margin, tile_position.y+1-margin}
}
end
function util.position_equal(position1, position2, epsilon)
epsilon = epsilon or 1e-6
if math.abs(position1.x - position2.x) > epsilon then return false end
if math.abs(position1.y - position2.y) > epsilon then return false end
return true
end
--[[
Compute a ray AABB intersection.
Algorithm from https://tavianator.com/2011/ray_box.html
"""
Axis-aligned bounding boxes (AABBs) are universally used to bound finite objects in ray-tracing.
Ray/AABB intersections are usually faster to calculate than exact ray/object intersections,
and allow the construction of bounding volume hierarchies (BVHs) which reduce the number of objects that need to be considered for each ray.
(More on BVHs in a later post.)
This means that a ray-tracer spends a lot of its time calculating ray/AABB intersections, and therefore this code ought to be highly optimised.
The fastest method for performing ray/AABB intersections is the slab method.
The idea is to treat the box as the space inside of three pairs of parallel planes.
The ray is clipped by each pair of parallel planes, and if any portion of the ray remains, it intersected the box.
"""
]]
function util.intersects_ray_aabb(ray_origin, orientation, aabb)
-- ray_vector will never be the zero-vector
local ray_vector = util.orientation_to_vector(orientation, 1)
local tmin = -math.huge
local tmax = math.huge
if (ray_vector.x ~= 0) then
local tx1 = (aabb.min_x - ray_origin.x) / ray_vector.x
local tx2 = (aabb.max_x - ray_origin.x) / ray_vector.x
tmin = math.max(tmin, math.min(tx1, tx2))
tmax = math.min(tmax, math.max(tx1, tx2))
else -- ray is going exactly vertically
return ray_origin.x >= aabb.min_x and ray_origin.x <= aabb.max_x
end
if (ray_vector.y ~= 0) then
local ty1 = (aabb.min_y - ray_origin.y) / ray_vector.y
local ty2 = (aabb.max_y - ray_origin.y) / ray_vector.y
tmin = math.max(tmin, math.min(ty1, ty2))
tmax = math.min(tmax, math.max(ty1, ty2))
else -- ray is going exactly horizontally
return ray_origin.y >= aabb.min_y and ray_origin.y <= aabb.max_y
end
return tmax >= tmin
end
function util.position_to_xy_string(position)
return util.xy_to_string(position.x, position.y)
end
function util.xy_to_string(x, y)
return util.floor(x) .. "_" .. util.floor(y)
end
function util.lerp(a, b, alpha)
return a + (b - a) * alpha
end
function util.lerp_angles(a, b, alpha)
local da = b - a
if da < -0.5 then
da = da + 1
elseif da > 0.5 then
da = da - 1
end
local na = a + da * alpha
if na < 0 then
na = na + 1
elseif na > 1 then
na = na - 1
end
return na
end
function util.array_to_vector(array)
return {x = array[1], y = array[2]}
end
function util.vectors_delta(a, b) -- from a to b
if not a and b then return 0 end
return {x = b.x - a.x, y = b.y - a.y}
end
function util.nvectors_delta(a, b) -- from a to b
local delta = {}
for i, coordinate in pairs(a) do
delta[i] = b[i] - a[i]
end
return delta
end
function util.vectors_delta_length(a, b)
return util.vector_length_xy(b.x - a.x, b.y - a.y)
end
function util.nvectors_delta_length(a, b)
return util.nvector_length(util.nvectors_delta(a, b))
end
function util.vector_length(a)
return util.sqrt(a.x * a.x + a.y * a.y)
end
function util.nvector_length(a)
local d = 0
for _, c in pairs(a) do
d = d + c * c
end
return util.sqrt(d)
end
function util.vector_length_xy(x, y)
return util.sqrt(x * x + y * y)
end
function util.vector_dot(a, b)
return a.x * b.x + a.y * b.y
end
function util.nvector_dot(a, b)
local d = 0
for i, c in pairs(a) do
d = d + a[i] * b[i]
end
return d
end
function util.vector_multiply(a, multiplier)
return {x = a.x * multiplier, y = a.y * multiplier}
end
function util.nvector_multiply(a, multiplier)
local d = {}
for i, c in pairs(a) do
d[i] = c*multiplier
end
return d
end
function util.vector_dot_projection(a, b)
local n = util.vector_normalise(a)
local d = util.vector_dot(n, b)
return {x = n.x * d, y = n.y * d}
end
function util.vector_normalise(a)
local length = util.vector_length(a)
return {x = a.x/length, y = a.y/length}
end
function util.nvector_normalise(a)
local length = util.nvector_length(a)
local d = {}
for i, c in pairs(a) do
d[i] = c/length
end
return d
end
function util.nvector_normalise(a)
local length = util.nvector_length(a)
local normalised = {}
for i, coordinate in pairs(a) do
normalised[i] = coordinate / length
end
return normalised
end
function util.vector_set_length(a, length)
local old_length = util.vector_length(a)
if old_length == 0 then return {x = 0, y = -length} end
return {x = a.x/old_length*length, y = a.y/old_length*length}
end
function util.vector_cross(a, b)
-- N = i(a2b3 - a3b2) + j(a3b1 - a1b3) + k(a1b2 - a2b1)
return {
x = (a.y or a[2]) * (b.z or b[3]) - (a.z or a[3]) * (b.y or b[2]),
y = (a.z or a[3]) * (b.x or b[1]) - (a.x or a[1]) * (b.z or b[3]),
z = (a.x or a[1]) * (b.y or b[2]) - (a.y or a[2]) * (b.x or b[1]),
}
end
function util.orientation_from_to(a, b)
return util.vector_to_orientation_xy(b.x - a.x, b.y - a.y)
end
function util.orientation_to_vector(orientation, length)
return {x = length * util.sin(orientation * 2 * util.pi), y = -length * util.cos(orientation * 2 * util.pi)}
end
function util.rotate_vector(orientation, a)
if orientation == 0 then
return {x = a.x, y = a.y}
else
return {
x = -a.y * util.sin(orientation * 2 * util.pi) + a.x * util.sin((orientation + 0.25) * 2 * util.pi),
y = a.y * util.cos(orientation * 2 * util.pi) -a.x * util.cos((orientation + 0.25) * 2 * util.pi)}
end
end
function util.vectors_add(a, b)
return {x = a.x + b.x, y = a.y + b.y}
end
function util.vectors_add3(a, b, c)
return {x = a.x + b.x + c.x, y = a.y + b.y + c.y}
end
function util.lerp_vectors(a, b, alpha)
return {x = a.x + (b.x - a.x) * alpha, y = a.y + (b.y - a.y) * alpha}
end
function util.move_to(a, b, max_distance, eliptical)
-- move from a to b with max_distance.
-- if eliptical, reduce y change (i.e. turret muzzle flash offset)
local eliptical_scale = 0.9
local delta = util.vectors_delta(a, b)
if eliptical then
delta.y = delta.y / eliptical_scale
end
local length = util.vector_length(delta)
if (length > max_distance) then
local partial = max_distance / length
delta = {x = delta.x * partial, y = delta.y * partial}
end
if eliptical then
delta.y = delta.y * eliptical_scale
end
return {x = a.x + delta.x, y = a.y + delta.y}
end
function util.vector_to_orientation(v)
return util.vector_to_orientation_xy(v.x, v.y)
end
function util.vector_to_orientation_xy(x, y)
return util.atan2(y, x) / util.pi / 2
end
function util.direction_to_orientation(direction)
if direction == defines.direction.north then
return 0
elseif direction == defines.direction.northeast then
return 0.125
elseif direction == defines.direction.east then
return 0.25
elseif direction == defines.direction.southeast then
return 0.375
elseif direction == defines.direction.south then
return 0.5
elseif direction == defines.direction.southwest then
return 0.625
elseif direction == defines.direction.west then
return 0.75
elseif direction == defines.direction.northwest then
return 0.875
end
return 0
end
function util.direction_to_string(direction)
if direction == defines.direction.north then
return "north"
elseif direction == defines.direction.northeast then
return "northeast"
elseif direction == defines.direction.east then
return "east"
elseif direction == defines.direction.southeast then
return "southeast"
elseif direction == defines.direction.south then
return "south"
elseif direction == defines.direction.southwest then
return "southwest"
elseif direction == defines.direction.west then
return "west"
elseif direction == defines.direction.northwest then
return "northwest"
end
return 0
end
function util.signal_to_string(signal)
return signal.type .. "__" .. signal.name
end
function util.signal_container_add(container, signal, count)
if signal then
if not container[signal.type] then
container[signal.type] = {}
end
if container[signal.type][signal.name] then
container[signal.type][signal.name].count = container[signal.type][signal.name].count + count
else
container[signal.type][signal.name] = {signal = signal, count = count}
end
end
end
function util.signal_container_add_inventory(container, entity, inventory)
local inv = entity.get_inventory(inventory)
if inv then
local contents = inv.get_contents()
for item_type, item_count in pairs(contents) do
util.signal_container_add(container, {type="item", name=item_type}, item_count)
end
end
end
function util.signal_container_get(container, signal)
if container[signal.type] and container[signal.type][signal.name] then
return container[signal.type][signal.name]
end
end
function util.signal_from_wires(red, green, signal)
local value = 0
if red then value = value + red.get_signal(signal) or 0 end
if green then value = value + green.get_signal(signal) or 0 end
return value
end
util.char_to_multiplier = {
m = 0.001,
c = 0.01,
d = 0.1,
h = 100,
k = 1000,
M = 1000000,
G = 1000000000,
T = 1000000000000,
P = 1000000000000000,
}
function util.string_to_number(str)
str = ""..str
local number_string = ""
local last_char = nil
for i = 1, #str do
local c = str:sub(i,i)
if c == "." or (c == "-" and i == 1) or tonumber(c) ~= nil then
number_string = number_string .. c
else
last_char = c
break
end
end
if last_char and util.char_to_multiplier[last_char] then
return tonumber(number_string) * util.char_to_multiplier[last_char]
end
return tonumber(number_string)
end
function util.replace(str, what, with)
what = util.str_gsub(what, "[%(%)%.%+%-%*%?%[%]%^%$%%]", "%%%1") -- escape pattern
with = util.str_gsub(with, "[%%]", "%%%%") -- escape replacement
str = util.str_gsub(str, what, with)
return str --only return the first variable from str_gsub
end
function util.split(s, delimiter)
result = {};
for match in (s..delimiter):gmatch("(.-)"..delimiter) do
table.insert(result, match);
end
return result;
end
function util.string_join (str_table, sep)
local str = ""
for _, str_part in pairs(str_table) do
if str ~= "" then
str = str .. sep
end
str = str .. str_part
end
return str
end
function util.parse_with_prefix(s, prefix)
if string.sub(s, 1, string.len(prefix)) == prefix then
return string.sub(s, string.len(prefix)+1)
end
end
function util.overwrite_table(table_weak, table_strong)
for k,v in pairs(table_strong) do table_weak[k] = v end
return table_weak
end
function util.table_contains(table, check)
for k,v in pairs(table) do if v == check then return true end end
return false
end
function util.table_to_string(table)
return serpent.block( table, {comment = false, numformat = '%1.8g' } )
end
function util.values_to_string(table)
local string = ""
for _, value in pairs(table) do
string = ((string == "") and "" or ", ") .. string .. value
end
return string
end
function util.math_log(value, base)
--logb(a) = logc(a) / logc(b)
return math.log(value)/math.log(base)
end
function util.seconds_to_clock(seconds, use_days)
local seconds = tonumber(seconds)
if seconds <= 0 then
return "0";
else
local days = 0
if use_days then days = math.floor(seconds/3600/24) end
local hours = math.floor(seconds/3600 - days*24)
local mins = math.floor(seconds/60 - hours*60 - days*1440)
local secs = math.floor(seconds - mins *60 - hours*3600 - days*86400)
local s_hours = string.format("%02.f",hours);
local s_mins = string.format("%02.f", mins);
local s_secs = string.format("%02.f", secs);
if days > 0 then
return days.."d:"..s_hours..":"..s_mins..":"..s_secs
end
if hours > 0 then
return s_hours..":"..s_mins..":"..s_secs
end
if mins > 0 then
return s_mins..":"..s_secs
end
if secs == 0 then
return "0"
end
return s_secs
end
end
function util.to_rail_grid(number_or_position)
if type(number_or_position) == "table" then
return {x = util.to_rail_grid(number_or_position.x), y = util.to_rail_grid(number_or_position.y)}
end
return math.floor(number_or_position / 2) * 2
end
function util.format_fuel(fuel, ceil)
return string.format("%.2f",(fuel or 0) / 1000).."k"
end
function util.format_energy(fuel, ceil)
if ceil then
return math.ceil((fuel or 0) / 1000000000).."GJ"
else
return math.floor((fuel or 0) / 1000000000).."GJ"
end
end
function util.direction_to_vector (direction)
if direction == defines.direction.east then return {x=1,y=0} end
if direction == defines.direction.north then return {x=0,y=-1} end
if direction == defines.direction.northeast then return {x=1,y=-1} end
if direction == defines.direction.northwest then return {x=-1,y=-1} end
if direction == defines.direction.south then return {x=0,y=1} end
if direction == defines.direction.southeast then return {x=1,y=1} end
if direction == defines.direction.southwest then return {x=-1,y=1} end
if direction == defines.direction.west then return {x=-1,y=0} end
end
function util.sign(x)
if x<0 then
return -1
elseif x>0 then
return 1
else
return 0
end
end
function util.find_first_descendant_by_name(gui_element, name)
for _, child in pairs(gui_element.children) do
if child.name == name then
return child
end
local found = util.find_first_descendant_by_name(child, name)
if found then return found end
end
end
function util.find_descendants_by_name(gui_element, name, all_found)
local found = all_found or {}
for _, child in pairs(gui_element.children)do
if child.name == name then
table.insert(found, child)
end
util.find_descendants_by_name(child, name, found)
end
return found
end
function util.swap_entity_inventories(entity_a, entity_b, inventory)
util.swap_inventories(entity_a.get_inventory(inventory), entity_b.get_inventory(inventory))
end
function util.swap_inventories(inv_a, inv_b)
if inv_a.is_filtered() then
for i = 1, math.min(#inv_a, #inv_b) do
inv_b.set_filter(i, inv_a.get_filter(i))
end
end
for i = 1, math.min(#inv_a, #inv_b)do
inv_b[i].swap_stack(inv_a[i])
end
end
function util.find_damage_types_from_trigger_items(trigger_items)
local damage_types = {}
for _, trigger_item in pairs(trigger_items) do
if trigger_item.action_delivery then
for _, action_delivery in pairs(trigger_item.action_delivery) do
if action_delivery.target_effects then --action_delivery.type == "instant"
for _, target_effect in pairs(action_delivery.target_effects) do
if target_effect.type == "damage" and target_effect.damage and target_effect.damage.type then
damage_types[target_effect.damage.type] = true
end
end
end
--"instant", "projectile", "flame-thrower", "beam", "stream", "artillery".
local beam_or_projectile
if action_delivery.beam then
beam_or_projectile = game.entity_prototypes[action_delivery.beam]
end
if action_delivery.projectile then
beam_or_projectile = game.entity_prototypes[action_delivery.projectile]
end
if beam_or_projectile then
if beam_or_projectile.attack_result then
local damage_types_2 = util.find_damage_types_from_trigger_items(beam_or_projectile.attack_result)
for damage_type, b in pairs(damage_types_2) do
damage_types[damage_type] = true
end
end
if beam_or_projectile.final_attack_result then
local damage_types_2 = util.find_damage_types_from_trigger_items(beam_or_projectile.final_attack_result)
for damage_type, b in pairs(damage_types_2) do
damage_types[damage_type] = true
end
end
end
end
end
end
return damage_types
end
function util.separate_points(positions, separation, max_iterations)
positions = table.deepcopy(positions)
--if true then return positions end
if not max_iterations then max_iterations = 20 end
local overshoot = 1.05 -- increse slightly to allow early finish
local i = 1
local continue = true
while i <= max_iterations and continue do
i = i + 1
continue = false
local forces = {}
for a, pos_a in pairs(positions) do
for b, pos_b in pairs(positions) do
if a ~= b then
local delta = Util.vectors_delta(pos_a, pos_b)
local length = Util.vector_length(delta)
if length < separation then
local force = Util.vector_set_length(delta, (separation - length) / 2)
if forces[a] then
forces[a].x = forces[a].x + force.x
forces[a].y = forces[a].y + force.y
else
forces[a] = force
end
end
end
end
end
for a, pos_a in pairs(positions) do
local force = forces[a]
if force then
local length = Util.vector_length(force)
if length > separation / 2 then
force = Util.vector_set_length(force, separation / 2)
end
pos_a.x = pos_a.x - force.x * overshoot
pos_a.y = pos_a.y - force.y * overshoot
continue = true
end
end
end
return positions
end
function util.safe_destroy(entity)
if not entity.valid then return end
if entity.type == "linked-container" then
entity.link_id = 0
entity.destroy({raise_destroy=true})
else
entity.destroy({raise_destroy=true})
end
end
function util.HSVToRGB( hue, saturation, value )
--https://gist.github.com/GigsD4X/8513963
-- Returns the RGB equivalent of the given HSV-defined color
-- (adapted from some code found around the web)
-- If it's achromatic, just return the value
if saturation == 0 then
return value;
end;
-- Get the hue sector
local hue_sector = math.floor( hue / 60 );
local hue_sector_offset = ( hue / 60 ) - hue_sector;
local p = value * ( 1 - saturation );
local q = value * ( 1 - saturation * hue_sector_offset );
local t = value * ( 1 - saturation * ( 1 - hue_sector_offset ) );
if hue_sector == 0 then
return value, t, p;
elseif hue_sector == 1 then
return q, value, p;
elseif hue_sector == 2 then
return p, value, t;
elseif hue_sector == 3 then
return p, q, value;
elseif hue_sector == 4 then
return t, p, value;
elseif hue_sector == 5 then
return value, p, q;
end;
end;
function util.setup_blueprint(event, names, serialize_fn)
if type(names) == 'string' then names = {names} end
local player_index = event.player_index
if player_index and game.players[player_index] and game.players[player_index].connected then
local player = game.players[player_index]
-- this setup code and checks is a workaround for the fact that the event doesn't specify the blueprint on the event
-- and the player.blueprint_to_setup isn't actually set in the case of copy/paste or blueprint library or select new contents
local blueprint = nil
if player and player.blueprint_to_setup and player.blueprint_to_setup.valid_for_read then blueprint = player.blueprint_to_setup
elseif player and player.cursor_stack.valid_for_read and player.cursor_stack.is_blueprint then blueprint = player.cursor_stack end
if blueprint and blueprint.is_blueprint_setup() then
local mapping = event.mapping.get()
local blueprint_entities = blueprint.get_blueprint_entities()
if blueprint_entities then
for _, blueprint_entity in pairs(blueprint_entities) do
if util.table_contains(names, blueprint_entity.name) then
local entity = mapping[blueprint_entity.entity_number]
if entity then
local tags = serialize_fn(entity)
if tags then
blueprint.set_blueprint_entity_tags(blueprint_entity.entity_number, tags)
end
end
end
end
end
end
end
end
function util.settings_pasted(event, names, serialize_fn, deserialize_fn, cb)
if type(names) == 'string' then names = {names} end
local player_index = event.player_index
if player_index and game.players[player_index] and game.players[player_index].connected
and event.source and event.source.valid and event.destination and event.destination.valid then
if not util.table_contains(names, event.source.name) then return end
if not util.table_contains(names, event.destination.name) then return end
local tags = serialize_fn(event.source)
if tags then
deserialize_fn(event.destination, tags)
if cb then cb(event.destination, player_index) end
end
end
end
function util.get_entity_from_event(event)
local entity
if event.entity and event.entity.valid then
entity = event.entity
end
if event.created_entity and event.created_entity.valid then
entity = event.created_entity
end
if event.destination and event.destination.valid then
entity = event.destination
end
return entity
end
function util.get_tags_from_event(event, serialize_fn)
local tags = event.tags
if not tags then
if event.source and event.source.valid then
tags = serialize_fn(event.source)
end
end
return tags
end
function util.find_entity_or_revive_ghost(surface, name, position, radius)
local entities = surface.find_entities_filtered{
name = name,
position = position,
radius = radius
}
if entities[1] and entities[1].valid then
return entities[1]
end
local entity_ghosts = surface.find_entities_filtered{
ghost_name = name,
position = position,
radius = radius
}
if entity_ghosts[1] and entity_ghosts[1].valid then
local collisions, entity = entity_ghosts[1].revive({})
if entity then return entity end
end
end
return util