This is a WIP (requires some bits of Stdlib; Position and Tile):
Code: Select all
-- luacheck: globals script global game Position Tile
_G.cursor = {}
--[[ INTERFACE ]]
function _G.cursor.latch( player, itemName, onCancel, data )
global.cursor = global.cursor or {}
global.cursor[player.name] = { itemName = itemName, onCancel = onCancel, data = data }
end
function _G.cursor.state( player )
global.cursor = global.cursor or {}
return global.cursor[player.name]
end
function _G.cursor.unlatch( player )
global.cursor = global.cursor or {}
global.cursor[player.name] = nil
end
function _G.cursor.pickUp( player, itemName, cursor )
player.clean_cursor()
cursor = cursor or player.cursor_stack
local stack = type(itemName) == 'table' and itemName or { name = itemName }
if cursor.can_set_stack( stack ) then
cursor.set_stack( stack )
game.print('picked up '..itemName)
return true
else
game.print('failed to pick up '..itemName)
return false
end
end
--[[ HELPERS ]]--
-- is itemName inside target?
local function isInside( target, itemName )
if not target then return end
if target.get_item_count( itemName ) > 0 then
target.remove_item( itemName )
game.print 'found in an entity'
return true
end
end
-- is itemName on floor near player?
local function dropNear( player, itemName )
-- doesn't search belts: https://forums.factorio.com/viewtopic.php?p=213509#p213509
local found = player.surface.find_entities_filtered {
type = 'item-entity';
name = 'item-on-ground';
area = Position.expand_to_area( Tile.from_position( player.position ), 32 );
}
for _,item in pairs(found) do
if item.stack.valid_for_read and item.stack.name == itemName then
item.destroy()
game.print 'found on floor'
return true
end
end
end
--[[ EVENTS ]]-- events.lua
function _G.cursor.onChange( player )
local monitor = global.cursor and global.cursor[player.name]
if monitor then
local itemName = monitor.itemName
local cursor = player.cursor_stack
if cursor.valid_for_read and cursor.name == itemName then
return -- these are not the droids you are looking for...
end
local selected = player.selected
local opened = player.opened
-- construction cancelled?
if isInside( player, itemName ) then
game.print 'player cancelled construction'
-- unlatch cursor
_G.cursor.unlatch( player )
-- trigger cancel event?
if monitor.onCancel then
game.print 'trigger cancel event'
monitor.onCancel( player, itemName, monitor.data )
end
return
end
-- find item and pick it up
-- TODO: vehicle?
if isInside( selected, itemName )
or isInside( opened , itemName )
or dropNear( player , itemName ) then
_G.cursor.pickUp( player, itemName )
else
game.print 'could not find monitored item'
end
end
end
It needs wiring up to the cursor change event like so:
Code: Select all
-- cursor stack change
script.on_event( defines.events.on_player_cursor_stack_changed, function( data )
local player = game.players[data.player_index]
_G.cursor.onChange( player )
end )
When you want to latch whatever the player is holding, use _G.cursor.latch()
Get current state for a player with _G.cursor.state()
Clear latch for player with _G.cursor.unlatch()
While latched:
* Player can cancel build by pressing Q or putting cursor stack in their quickbar or inventory
* If they try dropping it, or putting it in some other inventory (eg. chest, assembler, etc) it won't be allowed
Seems to work pretty well in my current experimenting, although I've not tested stuff like vehicles yet. As for reach distance, meh. Hopefully that `on_player_dropped_item ` (or similar) event can be added to mitigate issues with reach distance (and also remove need to to area surface search for item-entity entities).
Also, for my use case, the stack size is always 1 so I've not bothered testing for larger stack sizes.