664 lines
20 KiB
Lua
664 lines
20 KiB
Lua
|
|
---@class WhereIsMyBody : module
|
|
local M = {}
|
|
|
|
|
|
--#region Global data
|
|
---@type table<integer, any[]> # [render id, corpse entity, tag number?]
|
|
local players_bodies
|
|
---@type table<integer, any[]> # [render id, corpse entity, tag number?]
|
|
local inactive_players_bodies
|
|
---@type table<integer, table> # {render id, corpse entity, tag number?}
|
|
local important_players_body
|
|
---@type table<integer, table> # {render id, corpse entity, tag number?}
|
|
local inactive_important_players_body
|
|
---@type table<integer, LuaEntity>
|
|
local corpses_queue
|
|
--#endregion
|
|
|
|
|
|
--#region Constants
|
|
local draw_line = rendering.draw_line
|
|
local set_color = rendering.set_color
|
|
local rendering_destroy = rendering.destroy
|
|
local is_render_valid = rendering.is_valid
|
|
local remove = table.remove
|
|
--#endregion
|
|
|
|
|
|
--#region Settings
|
|
local update_tick = settings.global["WHMB_update_tick"].value
|
|
--#endregion
|
|
|
|
|
|
--#region Utils
|
|
|
|
local function remove_lines_event(event)
|
|
local player_index = event.player_index
|
|
local player_bodies = players_bodies[player_index]
|
|
if player_bodies ~= nil then
|
|
for i=1, #player_bodies do
|
|
local body_data = player_bodies[i]
|
|
rendering_destroy(body_data[1])
|
|
local chart_tag = body_data[3]
|
|
if chart_tag and chart_tag.valid then
|
|
chart_tag.destroy()
|
|
end
|
|
end
|
|
end
|
|
players_bodies[player_index] = nil
|
|
|
|
player_bodies = inactive_players_bodies[player_index]
|
|
if player_bodies ~= nil then
|
|
for i=1, #player_bodies do
|
|
local body_data = player_bodies[i]
|
|
rendering_destroy(body_data[1])
|
|
local chart_tag = body_data[3]
|
|
if chart_tag and chart_tag.valid then
|
|
chart_tag.destroy()
|
|
end
|
|
end
|
|
end
|
|
inactive_players_bodies[player_index] = nil
|
|
|
|
local body_data = important_players_body[player_index]
|
|
if body_data then
|
|
rendering_destroy(body_data[1])
|
|
important_players_body[player_index] = nil
|
|
local chart_tag = body_data[3]
|
|
if chart_tag and chart_tag.valid then
|
|
chart_tag.destroy()
|
|
end
|
|
end
|
|
body_data = inactive_important_players_body[player_index]
|
|
if body_data then
|
|
rendering_destroy(body_data[1])
|
|
inactive_important_players_body[player_index] = nil
|
|
local chart_tag = body_data[3]
|
|
if chart_tag and chart_tag.valid then
|
|
chart_tag.destroy()
|
|
end
|
|
end
|
|
end
|
|
|
|
local color_data = {0, 0, 0, 0} -- orange
|
|
local min_color_data = {0.19, 0.8 * 0.19, 0, 0.19} -- orange
|
|
local max_color_data = {0.9, 0.8 * 0.9, 0, 0.9} -- orange
|
|
---@param character table #LuaEntity
|
|
---@param id integer
|
|
---@param corpse table #LuaEntity
|
|
local function update_color(character, id, corpse)
|
|
local start = character.position
|
|
local stop = corpse.position
|
|
local xdiff = start.x - stop.x
|
|
local ydiff = start.y - stop.y
|
|
local distance = (xdiff * xdiff + ydiff * ydiff)^0.5
|
|
|
|
if distance > 450 then
|
|
set_color(id, min_color_data)
|
|
elseif distance < 95 then
|
|
set_color(id, max_color_data)
|
|
else
|
|
local r = 1 - distance / 500
|
|
color_data[1] = r
|
|
color_data[2] = 0.8 * r
|
|
color_data[4] = r
|
|
set_color(id, color_data)
|
|
end
|
|
end
|
|
|
|
-- Perhaps, I should change it
|
|
local purple_color_data = {171 / 255, 64 / 255, 1, 0.8}
|
|
local max_purple_color_data = {171 / 255, 64 / 255, 1, 0.8}
|
|
local min_purple_color_data = {(171 / 255) * 0.15, (64 / 255) * 0.15, 0.15, 0.8}
|
|
---@param character table #LuaEntity
|
|
---@param id integer
|
|
---@param corpse table #LuaEntity
|
|
local function update_purple_color(character, id, corpse)
|
|
local start = character.position
|
|
local stop = corpse.position
|
|
local xdiff = start.x - stop.x
|
|
local ydiff = start.y - stop.y
|
|
local distance = (xdiff * xdiff + ydiff * ydiff)^0.5
|
|
|
|
if distance > 450 then
|
|
set_color(id, min_purple_color_data)
|
|
elseif distance < 166 then
|
|
set_color(id, max_purple_color_data)
|
|
else
|
|
local r = (1 - distance / 500) * 1.5
|
|
r = (r > 1 and 1) or r
|
|
purple_color_data[1] = (171 / 255) * r
|
|
purple_color_data[2] = (64 / 255) * r
|
|
purple_color_data[3] = r
|
|
set_color(id, purple_color_data)
|
|
end
|
|
end
|
|
|
|
local orange_color = {1, 0.8, 0, 0.9}
|
|
local purple_color = {171 / 255, 64 / 255, 1, 0.8}
|
|
local line_data = {
|
|
color = orange_color,
|
|
width = 0.2,
|
|
from = nil,
|
|
surface = nil,
|
|
players = {},
|
|
draw_on_ground = true,
|
|
only_in_alt_mode = true
|
|
} -- It's a bit messy, so be careful about desyncs
|
|
---@param player LuaEntity
|
|
---@param player_index number
|
|
local function draw_all_lines(player, player_index, event)
|
|
local character = player.character
|
|
if not (character and character.valid) then
|
|
remove_lines_event(event)
|
|
return
|
|
end
|
|
|
|
local player_bodies
|
|
local important_body
|
|
local is_entity_info_visible = player.game_view_settings.show_entity_info
|
|
if is_entity_info_visible then
|
|
player_bodies = players_bodies[player_index]
|
|
important_body = important_players_body[player_index]
|
|
else
|
|
player_bodies = inactive_players_bodies[player_index]
|
|
important_body = inactive_important_players_body[player_index]
|
|
end
|
|
|
|
line_data.surface = character.surface
|
|
line_data.from = character
|
|
line_data.width = player.mod_settings["WHMB_line_width"].value
|
|
line_data.players = {player_index}
|
|
if player_bodies then
|
|
-- There's a chance that some lines still exist due to LuaPlayer.ticks_to_respawn
|
|
for i=1, #player_bodies do
|
|
-- is it really safe?
|
|
rendering_destroy(player_bodies[i][1])
|
|
end
|
|
|
|
line_data.color = orange_color
|
|
for i=#player_bodies, 1, -1 do
|
|
local body_data = player_bodies[i]
|
|
local entity = body_data[2]
|
|
if entity.valid then
|
|
line_data.to = entity
|
|
body_data[1] = draw_line(line_data)
|
|
else
|
|
remove(player_bodies, i)
|
|
end
|
|
end
|
|
end
|
|
|
|
if important_body then
|
|
rendering_destroy(important_body[1])
|
|
line_data.color = purple_color
|
|
local entity = important_body[2]
|
|
if entity.valid then
|
|
line_data.to = entity
|
|
important_body[1] = draw_line(line_data)
|
|
else
|
|
if is_entity_info_visible then
|
|
important_players_body[player_index] = nil
|
|
else
|
|
inactive_important_players_body[player_index] = nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
---@param player table #LuaPlayer
|
|
---@param corpse table #LuaEntity
|
|
---@param player_index number
|
|
---@param is_forced? boolean
|
|
local function draw_new_line_to_body(player, corpse, player_index, is_forced)
|
|
local character = player.character
|
|
if not (character and character.valid) then return end
|
|
if not (corpse and corpse.valid) then return end
|
|
local surface = character.surface
|
|
if surface ~= corpse.surface then return end
|
|
if not is_forced then
|
|
local items_count = table_size(corpse.get_inventory(defines.inventory.character_corpse).get_contents())
|
|
if items_count <= player.mod_settings["WHMB_ignore_if_less_n_items"].value then return end
|
|
end
|
|
|
|
line_data.surface = surface
|
|
line_data.from = character
|
|
line_data.to = corpse
|
|
line_data.width = player.mod_settings["WHMB_line_width"].value
|
|
line_data.players = {player_index}
|
|
|
|
local chart_tag
|
|
if player.mod_settings["WHMB_create_chart_tags_after_death"].value then
|
|
local icon = {type="virtual", name="signal-info"}
|
|
chart_tag = player.force.add_chart_tag(
|
|
surface,
|
|
{position=corpse.position, text='[entity=character-corpse]' .. player.name, icon=icon}
|
|
)
|
|
end
|
|
|
|
corpses_queue[player_index] = nil -- maybe it can be buggy
|
|
|
|
local player_bodies
|
|
local is_entity_info_visible = player.game_view_settings.show_entity_info
|
|
if is_entity_info_visible then
|
|
player_bodies = players_bodies[player_index]
|
|
if player_bodies == nil then
|
|
if important_players_body[player_index] then
|
|
players_bodies[player_index] = players_bodies[player_index] or {}
|
|
player_bodies = players_bodies[player_index]
|
|
else
|
|
line_data.color = purple_color
|
|
local id = draw_line(line_data)
|
|
important_players_body[player_index] = {id, corpse, chart_tag}
|
|
return
|
|
end
|
|
end
|
|
else
|
|
player_bodies = inactive_players_bodies[player_index]
|
|
if player_bodies == nil then
|
|
if inactive_important_players_body[player_index] then
|
|
inactive_players_bodies[player_index] = inactive_players_bodies[player_index] or {}
|
|
player_bodies = inactive_players_bodies[player_index]
|
|
else
|
|
line_data.color = purple_color
|
|
local id = draw_line(line_data)
|
|
inactive_important_players_body[player_index] = {id, corpse, chart_tag}
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
line_data.color = orange_color
|
|
local id = draw_line(line_data)
|
|
player_bodies[#player_bodies+1] = {id, corpse, chart_tag}
|
|
end
|
|
|
|
---@param player table #LuaPlayer
|
|
---@param corpse table #LuaEntity
|
|
---@param player_index number
|
|
---@param is_forced? boolean
|
|
local function draw_important_line_to_body(player, corpse, player_index, is_forced)
|
|
local character = player.character
|
|
if not (character and character.valid) then return end
|
|
if not (corpse and corpse.valid) then return end
|
|
local surface = character.surface
|
|
if surface ~= corpse.surface then return end
|
|
if not is_forced then
|
|
local items_count = table_size(corpse.get_inventory(defines.inventory.character_corpse).get_contents())
|
|
if items_count <= player.mod_settings["WHMB_ignore_if_less_n_items"].value then return end
|
|
end
|
|
|
|
line_data.surface = surface
|
|
line_data.from = character
|
|
line_data.to = corpse
|
|
line_data.width = player.mod_settings["WHMB_line_width"].value
|
|
line_data.players = {player_index}
|
|
|
|
corpses_queue[player_index] = nil -- maybe it can be buggy
|
|
|
|
line_data.color = purple_color
|
|
local id = draw_line(line_data)
|
|
local is_entity_info_visible = player.game_view_settings.show_entity_info
|
|
if is_entity_info_visible then
|
|
important_players_body[player_index] = {id, corpse}
|
|
else
|
|
inactive_important_players_body[player_index] = {id, corpse}
|
|
end
|
|
end
|
|
|
|
--#endregion
|
|
|
|
--#region Functions of events
|
|
|
|
local function check_render()
|
|
local get_player = game.get_player
|
|
for player_index, all_bodies_data in pairs(players_bodies) do
|
|
local player = get_player(player_index)
|
|
if player and player.valid then
|
|
local character = player.character
|
|
if character and character.valid then
|
|
for i=#all_bodies_data, 1, -1 do
|
|
local body_data = all_bodies_data[i]
|
|
local corpse = body_data[2]
|
|
if corpse.valid then
|
|
local id = body_data[1]
|
|
if is_render_valid(id) then
|
|
update_color(character, id, corpse)
|
|
else
|
|
local chart_tag = body_data[3]
|
|
if chart_tag and chart_tag.valid then
|
|
chart_tag.destroy()
|
|
end
|
|
remove(all_bodies_data, i)
|
|
draw_new_line_to_body(player, corpse, player_index, true)
|
|
end
|
|
else
|
|
local chart_tag = body_data[3]
|
|
if chart_tag and chart_tag.valid then
|
|
chart_tag.destroy()
|
|
end
|
|
remove(all_bodies_data, i)
|
|
end
|
|
end
|
|
if #all_bodies_data == 0 then
|
|
players_bodies[player_index] = nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
for player_index, body_data in pairs(important_players_body) do
|
|
local player = get_player(player_index)
|
|
if player and player.valid then
|
|
local character = player.character
|
|
if character and character.valid then
|
|
local corpse = body_data[2]
|
|
if corpse.valid then
|
|
local id = body_data[1]
|
|
if is_render_valid(id) then
|
|
update_purple_color(character, id, corpse)
|
|
else
|
|
local chart_tag = body_data[3]
|
|
if chart_tag and chart_tag.valid then
|
|
chart_tag.destroy()
|
|
end
|
|
important_players_body[player_index] = nil
|
|
draw_important_line_to_body(player, corpse, player_index, true)
|
|
end
|
|
else
|
|
local chart_tag = body_data[3]
|
|
if chart_tag and chart_tag.valid then
|
|
chart_tag.destroy()
|
|
end
|
|
important_players_body[player_index] = nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function on_player_toggled_alt_mode(event)
|
|
local player_index = event.player_index
|
|
if event.alt_mode then
|
|
players_bodies[player_index] = inactive_players_bodies[player_index]
|
|
inactive_players_bodies[player_index] = nil
|
|
important_players_body[player_index] = inactive_important_players_body[player_index]
|
|
inactive_important_players_body[player_index] = nil
|
|
else
|
|
inactive_players_bodies[player_index] = players_bodies[player_index]
|
|
players_bodies[player_index] = nil
|
|
inactive_important_players_body[player_index] = important_players_body[player_index]
|
|
important_players_body[player_index] = nil
|
|
end
|
|
end
|
|
|
|
local function on_pre_player_removed(event)
|
|
local player_index = event.player_index
|
|
players_bodies[player_index] = nil
|
|
inactive_players_bodies[player_index] = nil
|
|
important_players_body[player_index] = nil
|
|
inactive_important_players_body[player_index] = nil
|
|
corpses_queue[player_index] = nil
|
|
end
|
|
|
|
local function on_console_command(event)
|
|
if event.command ~= "editor" then return end
|
|
remove_lines_event(event)
|
|
end
|
|
|
|
local function on_player_clicked_gps_tag(event)
|
|
local player_index = event.player_index
|
|
local player = game.get_player(player_index)
|
|
if not (player and player.valid) then return end
|
|
local character = player.character
|
|
if not (character and character.valid) then return end
|
|
local is_entity_info_visible = player.game_view_settings.show_entity_info
|
|
if is_entity_info_visible == false then return end
|
|
local surface = game.get_surface(event.surface)
|
|
if not (surface and surface.valid) then return end
|
|
|
|
local player_bodies = players_bodies[player_index]
|
|
local important_player_body = important_players_body[player_index]
|
|
local pos = event.position
|
|
if important_player_body then
|
|
local x = pos.x
|
|
local y = pos.y
|
|
local corpse = important_player_body[2]
|
|
if corpse.valid then
|
|
if corpse.surface == surface then
|
|
local pos2 = corpse.position
|
|
local xdiff = x - pos2.x
|
|
local ydiff = y - pos2.y
|
|
local distance = (xdiff * xdiff + ydiff * ydiff)^0.5
|
|
if distance <= 2 then
|
|
return
|
|
end
|
|
end
|
|
else
|
|
important_players_body[player_index] = nil
|
|
end
|
|
end
|
|
|
|
if player_bodies then
|
|
local x = pos.x
|
|
local y = pos.y
|
|
for i=#player_bodies, 1, -1 do
|
|
local body_data = player_bodies[i]
|
|
local corpse = body_data[2]
|
|
if corpse.valid then
|
|
if corpse.surface == surface then
|
|
local pos2 = corpse.position
|
|
local xdiff = x - pos2.x
|
|
local ydiff = y - pos2.y
|
|
local distance = (xdiff * xdiff + ydiff * ydiff)^0.5
|
|
if distance <= 2 then
|
|
rendering_destroy(body_data[1])
|
|
remove(player_bodies, i)
|
|
local important_body_data = important_player_body
|
|
draw_important_line_to_body(player, corpse, player_index, true)
|
|
if important_body_data then
|
|
rendering_destroy(important_body_data[1])
|
|
local entity = important_body_data[2]
|
|
if entity.valid then
|
|
draw_new_line_to_body(player, entity, player_index, true)
|
|
end
|
|
end
|
|
return
|
|
end
|
|
end
|
|
else
|
|
remove(player_bodies, i)
|
|
end
|
|
end
|
|
end
|
|
|
|
if player.cheat_mode then return end
|
|
|
|
local filter = {type="character-corpse", position=pos, radius=2}
|
|
local corpses = surface.find_entities_filtered(filter)
|
|
for i=1, #corpses do
|
|
local corpse = corpses[i]
|
|
if corpse.valid then
|
|
local items_count = table_size(corpse.get_inventory(defines.inventory.character_corpse).get_contents())
|
|
if items_count > 0 then
|
|
draw_new_line_to_body(player, corpse, player_index, true)
|
|
return
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function on_player_respawned(event)
|
|
local player_index = event.player_index
|
|
local player = game.get_player(player_index)
|
|
if not (player and player.valid) then return end
|
|
if player.cheat_mode then return end
|
|
|
|
draw_all_lines(player, player_index, event)
|
|
|
|
local corpse = corpses_queue[player_index]
|
|
corpses_queue[player_index] = nil
|
|
if not (corpse and corpse.valid) then return end
|
|
if settings.global["WHMB_delete_empty_bodies"].value then
|
|
local items_count = table_size(corpse.get_inventory(defines.inventory.character_corpse).get_contents())
|
|
if items_count == 0 then
|
|
corpse.destroy({raise_destroy=true})
|
|
return
|
|
end
|
|
end
|
|
draw_new_line_to_body(player, corpse, player_index)
|
|
end
|
|
|
|
local function on_player_died(event)
|
|
local player_index = event.player_index
|
|
local player = game.get_player(player_index)
|
|
if not (player and player.valid) then return end
|
|
if player.cheat_mode then return end
|
|
if player.mod_settings["WHMB_create_lines"].value == false then return end
|
|
local surface = player.surface
|
|
local position = player.position
|
|
local corpse = surface.find_entity("character-corpse", position)
|
|
if not (corpse and corpse.valid) then return end
|
|
|
|
corpses_queue[player_index] = corpse
|
|
end
|
|
|
|
--TODO: check tag content, prohibit deletion of chart tags if the ones belongs to another player
|
|
local function on_chart_tag_removed(event)
|
|
local player_index = event.player_index
|
|
if player_index == nil then return end
|
|
local player = game.get_player(player_index)
|
|
if not (player and player.valid) then return end
|
|
local tag = event.tag
|
|
if tag.valid == false then return end
|
|
|
|
local player_bodies
|
|
local important_body
|
|
local is_entity_info_visible = player.game_view_settings.show_entity_info
|
|
if is_entity_info_visible then
|
|
player_bodies = players_bodies[player_index]
|
|
important_body = important_players_body[player_index]
|
|
else
|
|
player_bodies = inactive_players_bodies[player_index]
|
|
important_body = inactive_important_players_body[player_index]
|
|
end
|
|
|
|
local tag_number = tag.tag_number
|
|
local filter = {type="character-corpse", position=tag.position, radius=2}
|
|
local corpses = player.surface.find_entities_filtered(filter)
|
|
for i=1, #corpses do
|
|
local corpse = corpses[i]
|
|
if corpse.valid then
|
|
if player_bodies ~= nil then
|
|
for j=1, #player_bodies do
|
|
local body_data = player_bodies[j]
|
|
local chart_tag = body_data[3]
|
|
if chart_tag and chart_tag.valid and chart_tag.tag_number == tag_number then
|
|
rendering_destroy(body_data[1])
|
|
remove(player_bodies, j)
|
|
if #player_bodies == 0 then
|
|
if is_entity_info_visible then
|
|
players_bodies[player_index] = nil
|
|
else
|
|
inactive_players_bodies[player_index] = nil
|
|
end
|
|
end
|
|
return
|
|
end
|
|
end
|
|
end
|
|
if important_body then
|
|
local chart_tag = important_body[3]
|
|
if chart_tag and chart_tag.valid and chart_tag.tag_number == tag_number then
|
|
rendering_destroy(important_body[1])
|
|
if is_entity_info_visible then
|
|
important_players_body[player_index] = nil
|
|
else
|
|
inactive_important_players_body[player_index] = nil
|
|
end
|
|
return
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function on_runtime_mod_setting_changed(event)
|
|
if event.setting == "WHMB_update_tick" then
|
|
local value = settings.global[event.setting].value
|
|
script.on_nth_tick(update_tick, nil)
|
|
update_tick = value
|
|
script.on_nth_tick(value, check_render)
|
|
end
|
|
end
|
|
|
|
--#endregion
|
|
|
|
|
|
--#region Pre-game stage
|
|
|
|
local function link_data()
|
|
players_bodies = global.players_bodies
|
|
inactive_players_bodies = global.inactive_players_bodies
|
|
important_players_body = global.important_players_body
|
|
inactive_important_players_body = global.inactive_important_players_body
|
|
corpses_queue = global.corpses_queue
|
|
end
|
|
|
|
local function update_global_data()
|
|
global.players_bodies = {}
|
|
global.inactive_players_bodies = {}
|
|
global.important_players_body = {}
|
|
global.inactive_important_players_body = {}
|
|
global.corpses_queue = global.corpses_queue or {}
|
|
|
|
link_data()
|
|
|
|
for player_index, corpse in pairs(corpses_queue) do
|
|
if corpse.valid == false then
|
|
corpses_queue[player_index] = nil
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
M.on_init = update_global_data
|
|
M.on_configuration_changed = function(event)
|
|
local mod_changes = event.mod_changes["m_WhereIsMyBody"]
|
|
if not (mod_changes and mod_changes.old_version) then return end
|
|
|
|
update_global_data()
|
|
global.inactive_players_data = nil -- old data
|
|
global.players = nil -- old data
|
|
end
|
|
M.on_load = link_data
|
|
|
|
--#endregion
|
|
|
|
|
|
M.events = {
|
|
-- [defines.events.on_game_created_from_scenario] = on_game_created_from_scenario,
|
|
-- [defines.events.on_pre_player_mined_item] = on_pre_player_mined_item,
|
|
-- [defines.events.on_character_corpse_expired] = on_character_corpse_expired,
|
|
-- [defines.events.on_pre_surface_cleared] = on_pre_surface_cleared,
|
|
-- [defines.events.on_pre_surface_deleted] = on_pre_surface_deleted,
|
|
[defines.events.on_runtime_mod_setting_changed] = on_runtime_mod_setting_changed,
|
|
[defines.events.on_player_respawned] = on_player_respawned,
|
|
[defines.events.on_pre_player_removed] = on_pre_player_removed,
|
|
[defines.events.on_player_died] = on_player_died,
|
|
[defines.events.on_player_left_game] = remove_lines_event,
|
|
[defines.events.on_player_changed_surface] = remove_lines_event,
|
|
[defines.events.on_player_toggled_alt_mode] = on_player_toggled_alt_mode,
|
|
[defines.events.on_console_command] = on_console_command, -- on_player_toggled_map_editor event seems doesn't work
|
|
[defines.events.on_player_clicked_gps_tag] = on_player_clicked_gps_tag,
|
|
-- [defines.events.on_chart_tag_modified] = on_chart_tag_modified,
|
|
[defines.events.on_chart_tag_removed] = on_chart_tag_removed
|
|
}
|
|
|
|
M.on_nth_tick = {
|
|
[update_tick] = check_render,
|
|
}
|
|
|
|
return M
|