First dev in 1.1: completely refactored control code, attempt spidertron

waypoints compatibility (in progress).
This commit is contained in:
Peter 2020-11-28 01:37:34 +08:00
parent a7d3854217
commit 3840ea92cc
18 changed files with 781 additions and 435 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.vscode/

View File

@ -46,3 +46,17 @@ Version: 0.3.3
Date: 2020-08-23
Bugfixes:
- Hotfix: fixed missing check for invalid entity
---------------------------------------------------------------------------------------------------
Version: 0.5.0
Date: 2020-11-28
Features:
- NOTE: BETA RELEASE - USE AT YOUR OWN RISK!
- Update mod to Factorio 1.1
- Completely refactor code
- Attempt at Spidertron Waypoints compatibility
- 0NOTICE: THIS VERSION IS A WORK IN PROGRESS - CHANGELOG IS NOT FINAL
- 1NOTICE: THIS VERSION IS A WORK IN PROGRESS - CHANGELOG IS NOT FINAL
- 2NOTICE: THIS VERSION IS A WORK IN PROGRESS - CHANGELOG IS NOT FINAL
- 3NOTICE: THIS VERSION IS A WORK IN PROGRESS - CHANGELOG IS NOT FINAL
- 4NOTICE: THIS VERSION IS A WORK IN PROGRESS - CHANGELOG IS NOT FINAL

View File

@ -6,453 +6,62 @@
* Spiderbot.
--]]
-- /c for i=0,5 do game.player.insert("spidertron"); end; for i=0,2 do game.player.insert("spidertron-remote") end
require("util")
local function give_tool(player, stack)
if player.clean_cursor() and player.cursor_stack and player.cursor_stack.can_set_stack(stack) then
if player.get_main_inventory() then
player.get_main_inventory().remove("squad-spidertron-remote-sel")
player.get_main_inventory().remove("squad-spidertron-remote")
end
player.cursor_stack.set_stack(stack)
return true
end
end
require("control.debug")
-- REMEMBER TO COMMENT DEBUG OUT IN RELEASE!!
-- REMEMBER TO COMMENT DEBUG OUT IN RELEASE!!
-- REMEMBER TO COMMENT DEBUG OUT IN RELEASE!!
-- REMEMBER TO COMMENT DEBUG OUT IN RELEASE!!
-- REMEMBER TO COMMENT DEBUG OUT IN RELEASE!!
local function give_link_tool(index)
local d = global.spidercontrol_spidersquad[index]
if d then
if #d.spiders > 0 and d.spiders[1].spider_entity.valid then --- NEED TO CHECK THIS!!!!!!!!!!!!!!!!! CAN WE REMOVE IT?
local player = game.players[index]
if give_tool(player, {name="squad-spidertron-link-tool",count=1}) then
player.cursor_stack.connected_entity=d.spiders[1].spider_entity
end
-- give_tool(player, {name="spidertron-link-tool",count=1})
else
give_tool(game.players[index], {name="squad-spidertron-unlink-tool",count=1})
end
end
end
local function squad_center(spidersquad)
local xbar=0
local ybar=0
local c=0
for i=1, #spidersquad do
c=c+1
local pos = spidersquad[i].position
xbar=xbar+pos.x
ybar=ybar+pos.y
end
return {xbar/c,ybar/c}
end
local function spiderbot_select(event)
local index = event.player_index
local spiders = event.entities
if event.item == "squad-spidertron-remote-sel" and #spiders > 0 then
local center = squad_center(spiders)
global.spidercontrol_spidersquad[index] = {spiders={}} -- some future proofing here
for i=1, #spiders do
local spider = spiders[i]
local pos = spider.position
table.insert(global.spidercontrol_spidersquad[index].spiders, {
spider_entity=spider,
d={pos.x-center[1],pos.y-center[2]} -- dx and dy
})
end
local player = game.players[index]
if give_tool(player, {name="squad-spidertron-remote",count=1}) then
player.cursor_stack.connected_entity=spiders[1]
end
elseif event.item == "squad-spidertron-unlink-tool" and #spiders > 0 then
if #global.spidercontrol_linked_squads > 0 then
-- This is highly unoptimised, because we're searching through the list of all spidertrons and comparing it to the spidertrons in the selection box. Not quite the worst case, because everytime we get a match we remove it from the search list and we terminate the search when nothing is left in the search list. Can have a large UPS impact spike for bases with many squads that are very large, when a large selection of spidertrons are to be unlinked
-- Is there a way to attach an attribute directly to an entity, so that we don't need to search the whole global table?? That would improve speed by a lot
local ids = {}
local force = game.players[index].force.index
for i=1, #spiders do
ids[#ids+1] = spiders[i].unit_number
end
for i,t in pairs(global.spidercontrol_linked_squads) do
if #ids == 0 then break end
if force == t.force then
local pos = t.target.position
local c = 0
for j, spider in pairs(t.spiders) do
if #ids == 0 then break end
for k,id in pairs(ids) do
if spider.spider_entity.unit_number == id then
global.spidercontrol_linked_squads[i].spiders[j] = nil
table.remove(ids,k)
c = c + 1
end
end
end
if c > 0 then
if t.target and t.target.valid then
game.forces[t.force].print({"", c.." spidertrons have been unlinked from a ", t.target.localised_name, " near [gps="..pos.x..","..pos.y.."]"})
else
game.forces[t.force].print(c.." spidertrons have been unlinked from an entity near [gps="..pos.x..","..pos.y.."]")
end
end
end
end
end
end
end
local function validate_spiders(t, msg)
local c=0
if t then
--for i, spider_ in pairs(t.spiders) do
for i, spider in pairs(t.spiders) do
local spider_entity = spider.spider_entity
if not spider_entity or not spider_entity.valid then
t.spiders[i] = nil
c=c+1
end
end
if c > 0 then
local str = c .. " units were destroyed or mined since the last position command was sent"
if type(msg) == "boolean" and msg == true then -- This is for messaging when a unit is destroyed
local pos = t.target.position
game.forces[t.force].print(str..". Position is near [gps="..pos.x..","..pos.y.."]")
else
game.players[msg].print(str) --this is causing crashes for one user. states that the player does not exist (why?) needs more research
end
end
return true
elseif type(msg) ~= "boolean" then
global.spidercontrol_spidersquad[msg] = {spiders={}}
end
end
local function spiderbot_designate(index, position, force)
local d_
local msg
if force then
d_ = global.spidercontrol_linked_squads[index]
msg = true
else
d_ = global.spidercontrol_spidersquad[index]
msg = index
end
if validate_spiders(d_, msg) then
local spidersquad = d_.spiders
local leader
local follow
if not force then
leader = d_.spider_leader
follow = game.players[index].is_shortcut_toggled("squad-spidertron-follow")
end
local l_d = {0,0}
if leader then
if spidersquad[leader] and spidersquad[leader].spider_entity.valid then
-- game.players[index].print("Leader "..leader)
l_d = spidersquad[leader].d
else
game.players[index].print("Leader destroyed") -- In case destroyed by biters/nuke/whatever
global.spidercontrol_spidersquad[index].spider_leader = nil
leader = nil
end
end
require("control.init")
require("control.remote")
require("control.give_remote")
require("control.player_select")
require("control.player_man_designate")
require("control.player_follow")
require("control.entity_follow")
require("control.functions")
-- require("control.select")
for i, spider_ in pairs(spidersquad) do
if i ~= leader or not follow then
local spider = spider_.spider_entity
local d = spider_.d
spider.autopilot_destination = {position.x+d[1]-l_d[1], position.y+d[2]-l_d[2]} -- leader dy and dx offsets so that the leader itself becomes the new mean of the squad.
end
end
end
end
------------------------------------------------------------------------
-- EVENTS
------------------------------------------------------------------------
local function spiderbot_follow(player)
if player.character then
if player.is_shortcut_toggled("squad-spidertron-follow") then
player.set_shortcut_toggled("squad-spidertron-follow", false)
else
player.set_shortcut_toggled("squad-spidertron-follow", true)
end
else
player.print({"", {"error.error-message-box-title"}, ": ", {"player-doesnt-exist", {"gui.character"}}, " (", {"controller.god"}, "): ", {"gui-mod-info.status-disabled"}})
end
end
local function initialize()
if global.spidercontrol_spidersquad == nil then
game.print("Create tables for spidertron control mod")
global.spidercontrol_linked_squads = {}
global.spidercontrol_spidersquad = {}
for _, player in pairs(game.players) do
global.spidercontrol_spidersquad[player.index] = {spider_leader = nil, spiders={}}
end
end
end
local function squad_leader_state(index)
local player = game.players[index]
if player.vehicle and player.vehicle.type == "spider-vehicle" then
local unit_no = player.vehicle.unit_number
if validate_spiders(global.spidercontrol_spidersquad[index], index) then
local d = global.spidercontrol_spidersquad[index].spiders
if d then
for i, spider in pairs(d) do
-- game.print(spider.spider_entity.unit_number)
if spider.spider_entity.unit_number == unit_no then
global.spidercontrol_spidersquad[index].spider_leader = i
break
end
end
end
end
elseif player.vehicle == nil and global.spidercontrol_spidersquad[index] ~= nil then -- Why is it possible for this to be nil?
global.spidercontrol_spidersquad[index].spider_leader = nil
end
end
script.on_init(initialize)
script.on_configuration_changed(initialize)
--commands.add_command("spiderbot_initialize_variables", "debug: ensure that all global tables are not nil (should not happen in a normal game)", initialize)
script.on_event(defines.events.on_player_alt_selected_area, spiderbot_select)
script.on_event(defines.events.on_player_selected_area, spiderbot_select)
script.on_event(defines.events.on_player_used_spider_remote, function(event)
local index = event.player_index
local player = game.players[index]
local cursor_stack = player.cursor_stack
if cursor_stack then -- how can a player use a remote without a cursor_stack though???
if cursor_stack.valid_for_read and event.success then
local cname = cursor_stack.name
if cname == "squad-spidertron-remote" then
player.set_shortcut_toggled("squad-spidertron-follow", false)
spiderbot_designate(index, event.position)
elseif cname == "spidertron-remote" then -- WARNING: We're only overriding for the player's spidertron if it's the vanilla spidertron remote. Does not cover modded remotes!
-- Alter dy and dx
local unit_no = event.vehicle.unit_number
local d_ = global.spidercontrol_spidersquad[index] -- Note : Decided against doing checks on a linked squad because it would involve checking that table which can be massively large (larger than player table)
if validate_spiders(d_, index) then
local spidersquad = d_.spiders
local leader = d_.spider_leader
-- HELLO: if you are reading this and have an idea how to optimize it pls let me know (Not really critical as it's not in the tick loop, but could be problematic for very large squads )
for i, spider in pairs(spidersquad) do --something something premature debugging evil, but seriously the amount of loops are worrying (for large squds).
if i ~= leader and spider.spider_entity.unit_number == unit_no then -- Don't alter dy and dx for the squad leader (leads to infinite walking)
local dest = event.position
local flat = {} -- repack the array (which is divided because of us storing dy and dx) into a flat one
for j, spider_ in pairs(spidersquad) do
if j == i then
flat[#flat+1] = {position = dest} -- need to predict where it will be and use that as a mean, not current location
else
flat[#flat+1] = spider_.spider_entity
end
end
local center = squad_center(flat)
-- tried to do something without calling this loop but it's the most reliable option
--very interesting problem : because the mean of the squad is dependent on the positions of each squad member, varying the dy/dx parameters of only one spider (originally the one we're moving) results in this one being scaled off the 'actual' target location - at very far distances from the squad mean this becomes very noticeable. This means we need to calculate the mean of the entire squad if one has changed position. I noticed this error because of the fact that the offset was not constant but proportional to distance away from the mean
for k, spider_ in pairs(spidersquad) do
if k == i then
global.spidercontrol_spidersquad[index].spiders[k].d = {
dest.x - center[1], --dx
dest.y - center[2] --dy
}
else
local pos = spider_.spider_entity.position
global.spidercontrol_spidersquad[index].spiders[k].d = {
pos.x - center[1], --dx
pos.y - center[2] --dy
}
end
end
-- game.print("dx"..dest.x - center[1].."dy"..dest.y - center[2])
break
end
end
end
elseif cname == "squad-spidertron-link-tool" then
if player.selected and player.selected.valid then
local selected = player.selected
local pos = selected.position
player.print({"", "Linked ".. #global.spidercontrol_spidersquad[index].spiders .. " spiders to ", selected.localised_name, " near [gps=" .. pos.x .. "," .. pos.y .. "]"})
global.spidercontrol_linked_squads[#global.spidercontrol_linked_squads+1] = {
force=player.force.index,
target=selected,
spiders=util.table.deepcopy(global.spidercontrol_spidersquad[index].spiders)
}
global.spidercontrol_spidersquad[index] = {spider_leader = nil, spiders = {}} -- We're taking away player control of this squad!
-- Probably should print the squad ID, the target entity id and other information
else
local vehicle = event.vehicle
vehicle.autopilot_destination = vehicle.position
end
end
end
end
-- Follow
script.on_event("squad-spidertron-follow", function(event)
-- squad_leader_state(event.player_index)
SpiderbotFollow(game.players[event.player_index])
end)
script.on_event(defines.events.on_player_driving_changed_state, function (event)
squad_leader_state(event.player_index)
end)
script.on_event(defines.events.on_player_died, function(event)
squad_leader_state(event.player_index)
-- link tool
script.on_event("squad-spidertron-link-tool", function(event)
GiveLinkTool(event.player_index)
end)
script.on_event(defines.events.on_lua_shortcut, function (event)
local name = event.prototype_name
if name == "squad-spidertron-follow" then
local index = event.player_index
squad_leader_state(index)
spiderbot_follow(game.players[index])
-- squad_leader_state(index)
SpiderbotFollow(game.players[index])
elseif name == "squad-spidertron-link-tool" then
give_link_tool(event.player_index)
GiveLinkTool(event.player_index)
end
end)
script.on_event(defines.events.on_player_created, function (event)
global.spidercontrol_spidersquad[event.player_index] = {spider_leader = nil, spiders = {}}
end)
script.on_event("squad-spidertron-remote", function(event)
give_tool(game.players[event.player_index], {name="squad-spidertron-remote-sel",count=1})
end)
script.on_event("squad-spidertron-follow", function(event)
squad_leader_state(event.player_index)
spiderbot_follow(game.players[event.player_index])
end)
script.on_event("squad-spidertron-switch-modes", function(event)
local player = game.players[event.player_index]
local cursor_stack = player.cursor_stack
if cursor_stack and cursor_stack.valid_for_read then
local name = cursor_stack.name
if name == "squad-spidertron-remote" then
give_tool(player, {name="squad-spidertron-remote-sel",count=1})
elseif name == "squad-spidertron-remote-sel" then
local e = global.spidercontrol_spidersquad[event.player_index]
if e.spiders[1] and e.spiders[1].spider_entity.valid and give_tool(player, {name="squad-spidertron-remote",count=1}) then
player.cursor_stack.connected_entity=e.spiders[1].spider_entity
end
-- -- Link pair
elseif name == "squad-spidertron-link-tool" then
give_tool(player, {name="squad-spidertron-unlink-tool",count=1})
elseif name == "squad-spidertron-unlink-tool" then
give_link_tool(event.player_index)
end
end
end)
script.on_event("squad-spidertron-link-tool", function(event)
give_link_tool(event.player_index)
script.on_nth_tick(settings.global["spidertron-follow-update-interval"].value, function(event)
UpdateFollow()
UpdateFollowEntity()
end)
-- -- - This stuff handles the link tool
-- script.on_event(defines.events.on_put_item, function(event)
-- local player = game.players[event.player_index]
-- local cursor_stack = player.cursor_stack
-- if cursor_stack and cursor_stack.valid_for_read then
-- if cursor_stack.name == "spidertron-link-tool" then
-- game.print("HELLO")
-- end
-- end
-- script.on_event(defines.events.on_spider_command_completed, function (event)
-- game.print(game.tick)
-- end)
-- script.on_event(defines.events.on_built_entity, function(event)
-- if event.created_entity.name == "spidertron-link-tool" then
-- event.created_entity.destroy()
-- -- give_tool(player, {name="spidertron-link-tool",count=1}) -- Not using because this can cause UPS lag if someone click-drags it within placement range!
-- game.print("HELLO")
-- end
-- end)
local mov_offset = settings.global["spidertron-follow-prediction-distance"].value --This is so the player stays within the spider squad when moving
local mov_offset_diagonal = math.sqrt(mov_offset^2/2)
local function pos_offset(position,dir)
local def_dir = defines.direction
local pos_x = position.x
local pos_y = position.y
if dir == def_dir.north then
pos_y = pos_y - mov_offset
elseif dir == def_dir.northeast then
-- game.print("ne")
pos_x = pos_x + mov_offset_diagonal
pos_y = pos_y - mov_offset_diagonal
elseif dir == def_dir.east then
-- game.print("e")
pos_x = pos_x + mov_offset
elseif dir == def_dir.southeast then
-- game.print("se")
pos_x = pos_x + mov_offset_diagonal
pos_y = pos_y + mov_offset_diagonal
elseif dir == def_dir.south then
-- game.print("s")
pos_y = pos_y + mov_offset
elseif dir == def_dir.southwest then
-- game.print("sw")
pos_x = pos_x - mov_offset_diagonal
pos_y = pos_y + mov_offset_diagonal
elseif dir == def_dir.west then
-- game.print("w")
pos_x = pos_x - mov_offset
else -- northwest
-- game.print("nw")
pos_x = pos_x - mov_offset_diagonal
pos_y = pos_y - mov_offset_diagonal
end
return {x=pos_x, y=pos_y}
end
local update_interval = settings.global["spidertron-follow-update-interval"].value
script.on_nth_tick(update_interval, function(event)
for _, player in pairs(game.players) do
if player.is_shortcut_toggled("squad-spidertron-follow") and player.controller_type ~= 0 then -- 0 => defines.character.ghost (DEAD)
local index = player.index
local chk = global.spidercontrol_spidersquad[index]
if chk and chk.spiders and #chk.spiders > 0 then
local p_pos = player.position
local pos = p_pos
if player.walking_state.walking then
local dir = player.walking_state.direction
pos = pos_offset(p_pos,dir)
end
spiderbot_designate(index, pos)
end
end
end
if #global.spidercontrol_linked_squads > 0 then -- Might put this on another update_interval loop so the lag can be adjusted accordingly. Use the old modulo trick for that.
for i,t in pairs(global.spidercontrol_linked_squads) do
-- local t = global.spidercontrol_linked_squads[i]
if t.spiders then
if t.target.valid then
if #t.spiders > 0 then
spiderbot_designate(i, t.target.position, true)
else
local pos = t.target.position
game.forces[t.force].print({"", "Spidertron squad has been destroyed or unlinked from ", t.target.localised_name, " near [gps="..pos.x..","..pos.y.."]"}) -- using force comms because this could be the death of a spidertron, not only removal
table.remove(global.spidercontrol_linked_squads,i)
end
else
local pos = t.spiders[1].spider_entity.position
game.forces[t.force].print("Target entity of spidertron squad has been destroyed or removed near [gps="..pos.x..","..pos.y.."]")
table.remove(global.spidercontrol_linked_squads,i)
end
end
end
end
script.on_load(function()
SpidertronWaypointsCompatibility()
end)

88
control/2dvec.lua Normal file
View File

@ -0,0 +1,88 @@
function IJMean(vectors)
local sumx = 0
local sumy = 0
for i=1, #vectors do
local vec = vectors[i]
sumx = vec.x + sumx
sumy = vec.y + sumy
end
return {
x = sumx/#vectors,
y = sumy/#vectors
}
end
function IJMeanEntity(entities)
local sumx = 0
local sumy = 0
for i=1, #entities do
local pos = entities[i].position
sumx = pos.x + sumx
sumy = pos.y + sumy
end
return {
x = sumx/#entities,
y = sumy/#entities
}
end
function IJDelta(vec1, vec2)
local dx = vec2.x - vec1.x
local dy = vec2.y - vec1.y
return { x = dx, y = dy }
end
function IJAdd(vec1, vec2)
return {
x = vec1.x + vec2.x,
y = vec1.y + vec2.y
}
end
function IJSub(vec1, vec2)
return {
x = vec1.x - vec2.x,
y = vec1.y - vec2.y
}
end
function IJAhead(vec, dir, dd)
local dd_diagonal = math.sqrt(dd^2/2)
local d = defines.direction
local x = vec.x
local y = vec.y
if dir == d.north then
y = y - dd
elseif dir == d.northeast then
-- game.print("ne")
x = x + dd_diagonal
y = y - dd_diagonal
elseif dir == d.east then
-- game.print("e")
x = x + dd
elseif dir == d.southeast then
-- game.print("se")
x = x + dd_diagonal
y = y + dd_diagonal
elseif dir == d.south then
-- game.print("s")
y = y + dd
elseif dir == d.southwest then
-- game.print("sw")
x = x - dd_diagonal
y = y + dd_diagonal
elseif dir == d.west then
-- game.print("w")
x = x - dd
else -- northwest
-- game.print("nw")
x = x - dd_diagonal
y = y - dd_diagonal
end
return {x=x, y=y}
end

9
control/debug.lua Normal file
View File

@ -0,0 +1,9 @@
commands.add_command(
"dvars",
"debug: dump spider control vars",
function (cmd)
-- game.print(serpent.block(global.spidercontrol_player_s[cmd.player_index]))
game.print(serpent.block(global.spidercontrol_linked_s))
end
)

11
control/entity_follow.lua Normal file
View File

@ -0,0 +1,11 @@
require("control.2dvec")
function UpdateFollowEntity()
local links = global.spidercontrol_linked_s
for i = 1, #links do
if (#links[i].s > 0) then
GotoEntity(i)
end
end
end

218
control/functions.lua Normal file
View File

@ -0,0 +1,218 @@
function SpidertronWaypointsCompatibility()
-- Compatability for Spidertron Waypoints
if remote.interfaces["SpidertronWaypoints"] then
SPIDERTRON_WAYPOINTS = true
-- local event_ids = remote.call("SpidertronWaypoints", "get_events")
-- local on_spidertron_given_new_destination = event_ids.on_spidertron_given_new_destination
-- script.on_event(on_spidertron_given_new_destination, function(event)
-- game.print("New destination")
-- Goto(global.spidercontrol_player_s[event.player_index].active, event.position)
-- end)
end
end
function GiveStack(player, stack)
if player.clear_cursor() and player.cursor_stack and player.cursor_stack.can_set_stack(stack) then
if player.get_main_inventory() then
player.get_main_inventory().remove("squad-spidertron-remote-sel")
player.get_main_inventory().remove("squad-spidertron-remote")
end
player.cursor_stack.set_stack(stack)
return true
end
end
function Remove(table, indices)
local n = #table
for i = 1, #indices do
table[indices[i]] = nil
end
local tmp = {}
for i = 1, n do
if (table[i] ~= nil) then
tmp[#tmp+1] = table[i]
end
end
return tmp
end
function Goto(spiders, center)
local invalid = {}
for i = 1, #spiders do
local spider = spiders[i].spider
local d = spiders[i].delta
if spider.valid then
spider.autopilot_destination = IJAdd(center, d)
else
invalid[#invalid+1] = i
end
end
return Remove(spiders, invalid) -- We return an updated spider list (Remove any invalid spiders)
end
local function drawSprite(surface, target, scale, force, tint)
return rendering.draw_sprite({
sprite="item/squad-spidertron-link-tool",
target = target,
surface = (target.surface or surface),
x_scale = scale,
y_scale = scale,
only_in_alt_mode = true,
forces = {force},
tint = (tint or {r=1,g=1,b=1})
})
end
-- rendering.draw_sprite({sprite="item/squad-spidertron-link-tool", target = game.player.selected, surface = game.player.surface, only_in_alt_mode = true, forces = {game.player.force}, tint = {r=1,g=1,b=1}})
local function resetSprites(indices)
if indices then
for i = 1, #indices do
rendering.destroy(indices[i])
end
end
end
-- Original GotoPlayer function before waypoints compat.
local function GotoPlayer_(index, position)
local active = global.spidercontrol_player_s[index].active
local active_n = #active
local active_updated = Goto(active, position)
global.spidercontrol_player_s[index].active = active_updated
if (active_n > #active_updated) then
local str = (active_n - #active_updated) .. " units were destroyed or mined"
game.players[index].print(str)
end
end
local function GotoPlayerSW(index, position)
local active = global.spidercontrol_player_s[index].active
local player = game.players[index]
local linear = player.is_shortcut_toggled("spidertron-remote-waypoint")
local cyclic = player.is_shortcut_toggled("spidertron-remote-patrol")
if (cyclic) then
active[1].spider.autopilot_destination = nil
local patrol = global.spidercontrol_spidertronwaypoints_patrol[index]
if (patrol) then
local start = rendering.get_target(patrol[1]).position
if (util.distance(position, start) < 5) then
for j = 1, #active do
local waypoints = {}
for i = 1, #patrol do
local position = IJAdd(
rendering.get_target(patrol[i]).position,
active[j].delta
)
waypoints[#waypoints+1] = {position = position}
end
remote.call("SpidertronWaypoints", "assign_patrol", active[j].spider, waypoints)
end
resetSprites(patrol)
global.spidercontrol_player_s[index].active = {}
GiveStack(player, {name="squad-spidertron-remote-sel",count=1})
player.set_shortcut_toggled("spidertron-remote-patrol", false)
patrol = nil
else
patrol[#patrol+1] = drawSprite(player.surface, position, 1.5, player.force, {r=1.0,g=0.0,b=1.0})
end
else
patrol = {drawSprite(player.surface, position, 1.5, player.force, {r=1.0,g=1.0,b=1.0})}
end
global.spidercontrol_spidertronwaypoints_patrol[index] = patrol
elseif (linear) then
local active_updated = Goto(active, position)
for i = 1, #active_updated do
local position = IJAdd(position, active_updated[i].delta)
local waypoint = {{position = position}}
remote.call("SpidertronWaypoints", "assign_waypoints", active_updated[i].spider, waypoint)
end
resetSprites(global.spidercontrol_spidertronwaypoints_patrol[index])
global.spidercontrol_spidertronwaypoints_patrol[index] = nil
else
GotoPlayer_(index, position)
resetSprites(global.spidercontrol_spidertronwaypoints_patrol[index])
global.spidercontrol_spidertronwaypoints_patrol[index] = nil
end
end
function GotoPlayer(index, position)
if SPIDERTRON_WAYPOINTS then
GotoPlayerSW(index, position)
else
GotoPlayer_(index, position)
end
end
function GotoEntity(index)
local t = global.spidercontrol_linked_s[index]
local entity = t.target
local active = t.s
if entity.valid then
local active_n = #active
local pos = entity.position
local active_updated = Goto(active, pos)
global.spidercontrol_linked_s[index].s = active_updated
if (active_n > #active_updated) then
local str = (active_n - #active_updated) .. " units were destroyed or mined near [gps="..pos.x..","..pos.y.."], linked to "..entity.localised_name
game.forces[t.force].print(str)
end
if (#active_updated == 0) then
local str = {"", "Spidertron squad has been destroyed or unlinked from ", entity.localised_name, " near [gps="..pos.x..","..pos.y.."]"}
game.forces[t.force].print(str) -- using force comms because this could be the death of a spidertron, not only removal
end
else
local e = false
for i = 1, #active do
if (active[i].spider.valid) then
e = active[i].spider
end
end
if (e) then
local pos = e.position
game.forces[t.force].print("Target entity of spidertron squad has been destroyed or removed near [gps="..pos.x..","..pos.y.."]")
else
game.forces[t.force].print("Target entity of spidertron squad has been destroyed or removed")
end
global.spidercontrol_linked_s = Remove(global.spidercontrol_linked_s, {index})
end
end
function FirstValid(entities)
for i = 1, #entities do
if entities[i] and entities[i].valid then
return entities[i]
end
end
return false
end
-- local function squad_center(spidersquad)
-- local xbar=0
-- local ybar=0
-- local c=0
-- for i=1, #spidersquad do
-- c=c+1
-- local pos = spidersquad[i].position
-- xbar=xbar+pos.x
-- ybar=ybar+pos.y
-- end
-- return {xbar/c,ybar/c}
-- end
function Mean(list)
local sum = 0
for i=1, #list do
sum=sum+list[i]
end
return sum/#list
end

51
control/give_remote.lua Normal file
View File

@ -0,0 +1,51 @@
require("control.functions")
function GiveLinkTool(index)
local d = global.spidercontrol_player_s[index].active
if (#d > 0 and d[1].spider.valid) then
local player = game.players[index]
if GiveStack(player, {name="squad-spidertron-link-tool",count=1}) then
player.cursor_stack.connected_entity = d[1].spider
end
else
GiveStack(game.players[index], {name="squad-spidertron-unlink-tool",count=1})
end
end
script.on_event("squad-spidertron-remote", function(event)
GiveStack(game.players[event.player_index], {name="squad-spidertron-remote-sel",count=1})
end)
script.on_event("squad-spidertron-switch-modes", function(event)
local player = game.players[event.player_index]
local cursor_stack = player.cursor_stack
if cursor_stack and cursor_stack.valid_for_read then
local name = cursor_stack.name
if name == "squad-spidertron-remote" then
GiveStack(player, {name="squad-spidertron-remote-sel",count=1})
elseif name == "squad-spidertron-remote-sel" then
local e = global.spidercontrol_player_s[event.player_index].active
if e[1] and e[1].spider.valid and GiveStack(player, {name="squad-spidertron-remote",count=1}) then
player.cursor_stack.connected_entity = e[1].spider
end
-- -- Link pair
elseif name == "squad-spidertron-link-tool" then
GiveStack(player, {name="squad-spidertron-unlink-tool",count=1})
elseif name == "squad-spidertron-unlink-tool" then
GiveLinkTool(event.player_index)
end
end
end)
-- script.on_event(defines.events.on_lua_shortcut, function (event)
-- local name = event.prototype_name
-- if name == "squad-spidertron-follow" then
-- local index = event.player_index
-- squad_leader_state(index)
-- spiderbot_follow(game.players[index])
-- elseif name == "squad-spidertron-link-tool" then
-- GiveLinkTool(event.player_index)
-- end
-- end)

17
control/init.lua Normal file
View File

@ -0,0 +1,17 @@
local function initialize()
if global.spidercontrol_spidersquad == nil then
game.print("Create tables for spidertron control mod")
global.spidercontrol_linked_s = {}
global.spidercontrol_player_s = {}
global.spidercontrol_spidertronwaypoints_patrol = {}
for _, player in pairs(game.players) do
global.spidercontrol_player_s[player.index] = {active = {}, inactive = {}} -- Some future-proofing here
end
end
SpidertronWaypointsCompatibility()
end
script.on_init(initialize)
script.on_configuration_changed(initialize)
-- commands.add_command("spiderbot_initialize_variables", "debug: ensure that all global tables are not nil (should not happen in a normal game)", initialize)

63
control/player_follow.lua Normal file
View File

@ -0,0 +1,63 @@
local function spidertronWaypointsOverride(s)
if SPIDERTRON_WAYPOINTS then
for i = 1, #s do
remote.call("SpidertronWaypoints", "clear_waypoints", s[i].spider.unit_number)
end
end
end
function SpiderbotFollow(player)
if player.character then
if player.is_shortcut_toggled("squad-spidertron-follow") then
player.set_shortcut_toggled("squad-spidertron-follow", false)
local index = player.index
GotoPlayer(index, player.position)
spidertronWaypointsOverride(global.spidercontrol_player_s[index].active)
else
player.set_shortcut_toggled("squad-spidertron-follow", true)
global.spidercontrol_player_s[player.index].p_pos = nil
end
else
player.print({"", {"error.error-message-box-title"}, ": ", {"player-doesnt-exist", {"gui.character"}}, " (", {"controller.god"}, "): ", {"gui-mod-info.status-disabled"}})
end
end
local mov_offset = settings.global["spidertron-follow-prediction-distance"].value --This is so the player stays within the spider squad when moving
function UpdateFollow()
for _, player in pairs(game.players) do
if (player.controller_type ~= 0 and player.is_shortcut_toggled("squad-spidertron-follow")) then -- 0 => defines.character.ghost (DEAD)
local index = player.index
local active = global.spidercontrol_player_s[index].active
if (active and #active > 0) then
local p_pos = global.spidercontrol_player_s[index].p_pos
local pos = player.position
if ( p_pos == nil or p_pos.x ~= pos.x or p_pos.y ~= pos.y ) then
local vehicle = player.vehicle
if (vehicle and vehicle.type == "spider-vehicle") then
local un = vehicle.unit_number
for i = 1, #active do
if (active[i].spider.unit_number == un) then
pos = IJSub(vehicle.position, active[i].delta)
break
end
end
end
-- player.print("running" .. game.tick)
if player.walking_state.walking then
local dir = player.walking_state.direction
pos = IJAhead(pos, dir, mov_offset)
end
GotoPlayer(index, pos)
spidertronWaypointsOverride(active)
global.spidercontrol_player_s[index].p_pos = player.position
end
end
end
end
end

View File

@ -0,0 +1,100 @@
require("control.functions")
local function moveTo(player, index, position)
game.players[index].set_shortcut_toggled("squad-spidertron-follow", false)
GotoPlayer(index, position)
end
local function realign(s, vehicle, position, target)
local unit_number = vehicle.unit_number
for i = 1, #s do
if (s[i].spider.unit_number == unit_number) then
-- This commented-out targeting works relative to the mean of the group, not the target entity. It has its benefits but ultimately I preferred the target-based modification, so i've left this uncommented.
-- if (s[i+1]) then -- Use another spidertron as a reference vector/position
-- local origin = IJSub(s[i+1].spider.position, s[i+1].delta)
-- s[i].delta = IJSub(position, origin)
-- elseif (s[i-1]) then
-- local origin = IJSub(s[i-1].spider.position, s[i-1].delta)
-- s[i].delta = IJSub(position, origin)
-- else
-- -- spider list should never be a sparse list, so if nothing's adjacent it's a single spidertron
-- s[i].delta = IJSub(position, vehicle.position) -- Not really good without a ref vector
-- end
s[i].delta = IJSub(position, target.position)
return s
end
end
return false
end
local function drawSprite(target, scale, force, tint, target_offset)
return rendering.draw_sprite({
sprite="item/squad-spidertron-link-tool",
target = target,
surface = target.surface,
x_scale = scale,
y_scale = scale,
only_in_alt_mode = true,
forces = {force},
tint = (tint or {r=1,g=1,b=1}),
target_offset = (target_offset or {x=0,y=0})
})
end
local function link(index, vehicle)
local player = game.players[index]
local n = #global.spidercontrol_player_s[index].active
if player.selected and player.selected.valid and n > 0 then
local selected = player.selected
local pos = selected.position
local scale = 1.5
local force = player.force
player.print({"", "Linked ".. n .. " spiders to ", selected.localised_name, " near [gps=" .. pos.x .. "," .. pos.y .. "]"})
local sprite = drawSprite(selected, scale, force)
local s = util.table.deepcopy(global.spidercontrol_player_s[index].active)
for i = 1, #s do
s[i].sprite = drawSprite(s[i].spider, scale, force, {r=1,g=0,b=0}, {x=0,y=-0.3})
end
global.spidercontrol_linked_s[#global.spidercontrol_linked_s+1] = {
force=player.force.index,
target=selected,
sprite=sprite,
s=s
}
global.spidercontrol_player_s[index].active = {} -- We're taking away player control of this squad!
-- Probably should print the squad ID, the target entity id and other information
GiveStack(player, {name="squad-spidertron-unlink-tool",count=1})
end
vehicle.autopilot_destination = vehicle.position -- Just to look better
end
script.on_event(defines.events.on_player_used_spider_remote, function(event)
local index = event.player_index
local player = game.players[index]
local cursor_stack = player.cursor_stack
if cursor_stack then -- how can a player use a remote without a cursor_stack though???
if cursor_stack.valid_for_read and event.success then
local cname = cursor_stack.name
if cname == "squad-spidertron-remote" then
moveTo(player, index, event.position)
elseif cname == "spidertron-remote" then -- WARNING: We're only overriding for the player's spidertron if it's the vanilla spidertron remote. Does not cover modded remotes!
local vehicle = event.vehicle
local position = event.position
local s = realign(global.spidercontrol_player_s[index].active, vehicle, position, player)
if s then
global.spidercontrol_player_s[index].active = s
else
local t = global.spidercontrol_linked_s
for i = 1, #t do
if realign(t[i].s, vehicle, position, t[i].target) then
global.spidercontrol_linked_s[i].t = t
end
end
end
elseif cname == "squad-spidertron-link-tool" then
link(index, event.vehicle)
end
end
end
end)

140
control/player_select.lua Normal file
View File

@ -0,0 +1,140 @@
require("control.functions")
require("control.2dvec")
local function messageSpiders(target, s, force, n)
if target.valid then
local pos = target.position
game.forces[force].print({"", n.." spidertrons have been unlinked from a ", target.localised_name, " near [gps="..pos.x..","..pos.y.."]"})
else
local e = FirstValid(s)
if e then
local pos = e.position
game.forces[force].print(n.." spidertrons have been unlinked from an entity near [gps="..pos.x..","..pos.y.."]")
else
game.forces[force].print(n.." spidertrons have been unlinked from an entity")
end
end
end
local function messageS(target, s, force)
if target.valid then
local pos = target.position
game.forces[force].print({"", "Spidertron squad has been unlinked from ", target.localised_name, " near [gps="..pos.x..","..pos.y.."]"}) -- using force comms because this could be the death of a spidertron, not only removal
else
local e = FirstValid(s)
if e then
local pos = e.position
game.forces[force].print({"", "Spidertron squad has been unlinked from a target near [gps="..pos.x..","..pos.y.."]"})
else
game.forces[force].print({"", "Spidertron squad has been unlinked from a target"})
end
end
end
local function unitNumbers(entities)
local ids = {}
for i = 1, #entities do
ids[#ids+1] = entities[i].unit_number
end
return ids
end
local function spiderDeSelect(spiders, force)
local ids = unitNumbers(spiders)
local t = global.spidercontrol_linked_s
local rem_t = {} --This part is really bad - I think it's O(N^3) worst case?
for i = 1, #t do
if (t[i].force == force and #ids > 0) then
local s = t[i].s
local rem_s = {}
for j = 1, #s do
local rem_id = {}
for k = 1, #ids do
if (ids[k] == s[j].spider.unit_number) then
s[j].spider.autopilot_destination = nil
rem_id[#rem_id+1] = k
rem_s[#rem_s+1] = j
-- game.print("REMOVE["..i.."]["..j.."]")
end
end
if (#rem_id > 0) then
ids = Remove(ids, rem_id)
if (#ids == 0) then
break
end
end
end
if (#rem_s > 0) then
local force = t[i].force
local target = t[i].target
for x = 1, #rem_s do
rendering.destroy(s[rem_s[x]].sprite)
end
s = Remove(s, rem_s)
messageSpiders(target, s, force, #rem_s)
if (#s == 0) then
rem_t[#rem_t+1] = i
messageS(target, s, force)
else
global.spidercontrol_linked_s[i].s = s
end
end
if (#ids == 0) then
break
end
end
end
if (#rem_t > 0) then -- Need to remove sprite
for x = 1, #rem_t do
rendering.destroy(t[rem_t[x]].sprite)
end
global.spidercontrol_linked_s = Remove(global.spidercontrol_linked_s, rem_t)
end
end
local function spiderSelect(spiders, index)
local player = game.players[index]
local force = player.force.index
local mean = IJMeanEntity(spiders)
global.spidercontrol_player_s[index].active = {}
spiderDeSelect(spiders, force) -- TO prevent double-linking (linking the same spider to 2 or more entities)
if SPIDERTRON_WAYPOINTS then
for i = 1, #spiders do
remote.call("SpidertronWaypoints", "clear_waypoints", spiders[i].unit_number)
spiders[i].autopilot_destination = nil
end
end
-- spiderDeSelectPlayers(spiders, force) -- Y'know what, not sure if i can be arsed to deselect player squads...
for i=1, #spiders do
table.insert(global.spidercontrol_player_s[index].active, {
spider = spiders[i],
delta = IJDelta(mean, spiders[i].position)
})
end
if GiveStack(player, {name="squad-spidertron-remote",count=1}) then
player.cursor_stack.connected_entity=spiders[1]
end
end
local function areaSelection(event)
local index = event.player_index
local spiders = event.entities -- We're only selecting spiders from our force (due to force filter)
local item = event.item
if #spiders > 0 then
if item == "squad-spidertron-remote-sel" then
spiderSelect(spiders, index)
elseif item == "squad-spidertron-unlink-tool" then
spiderDeSelect(spiders, spiders[1].force.index)
end
end
end
script.on_event(defines.events.on_player_alt_selected_area, areaSelection)
script.on_event(defines.events.on_player_selected_area, areaSelection)

2
control/remote.lua Normal file
View File

@ -0,0 +1,2 @@
-- Just need to claim this interface name.
remote.add_interface("SpiderControl", {})

View File

@ -6,4 +6,4 @@
* Data
--]]
require("shortcuts")
require("prototypes.shortcuts")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -1,7 +1,7 @@
{
"name": "Spider_Control",
"version": "0.4.0",
"factorio_version": "1.0",
"version": "0.5.0",
"factorio_version": "1.1",
"title": "Spidertron squad control",
"author": "npc_strider(morley376)",
"contact": "",

13
make.sh
View File

@ -1,6 +1,13 @@
# v=$(grep "\"version\"" info.json | sed 's|.*:||;s|[",]||g;s| ||g')
# n=$(grep "\"name\"" info.json | sed 's|.*:||;s|[",]||g;s| ||g')
# #echo ${n}_${v}.zip
# powershell -c "\$files = Get-ChildItem -Path . -Exclude .git,*.sh
# Compress-Archive -Path \$files -DestinationPath ../${n}_${v}.zip"
v=$(grep "\"version\"" info.json | sed 's|.*:||;s|[",]||g;s| ||g')
n=$(grep "\"name\"" info.json | sed 's|.*:||;s|[",]||g;s| ||g')
#echo ${n}_${v}.zip
echo ${n}_${v}.zip
cd ..
powershell -c "\$files = Get-ChildItem -Path . -Exclude .git,*.sh
Compress-Archive -Path \$files -DestinationPath ../${n}_${v}.zip"
7z a -tzip ${n}_${v}.zip ./${n} -xr'!.git' -xr'!.gitignore' -xr'!*.pdn'

View File

@ -8,6 +8,10 @@
require('util')
------------------------------------------------------------------------
-- ITEMS
------------------------------------------------------------------------
local item_remote_sel = {
type = "selection-tool",
name = "squad-spidertron-remote-sel",
@ -59,15 +63,19 @@ item_link.localised_name = "Spidertron link tool"
item_link.icon = "__Spider_Control__/graphics/icons/spidertron-link-tool.png"
item_link.icon_color_indicator_mask = "__Spider_Control__/graphics/icons/spidertron-link-tool-mask.png"
------------------------------------------------------------------------
-- SHORTCUTS
------------------------------------------------------------------------
local shortcut_remote = {
type = "shortcut",
name = "squad-spidertron-remote",
order = "a[squad-spidertron-remote]",
action = "create-blueprint-item",
action = "spawn-item",
localised_name = "Spidertron squad remote",
associated_control_input = "squad-spidertron-remote",
technology_to_unlock = "spidertron",
item_to_create = "squad-spidertron-remote-sel",
item_to_spawn = "squad-spidertron-remote-sel",
style = "red",
icon =
{
@ -110,6 +118,10 @@ shortcut_link.localised_name = "Link spidertrons to entity"
shortcut_link.associated_control_input = "squad-spidertron-link-tool"
shortcut_link.style = "green"
------------------------------------------------------------------------
-- CUSTOM INPUT
------------------------------------------------------------------------
local input_remote = {
type = "custom-input",
name = "squad-spidertron-remote",
@ -133,6 +145,10 @@ input_link.name = "squad-spidertron-link-tool"
input_link.localised_name = "Link spidertron squad to entity"
input_link.key_sequence = "ALT + Z"
------------------------------------------------------------------------
-- EXTEND
------------------------------------------------------------------------
data:extend(
{
shortcut_remote,