Merge remote-tracking branches 'warmist/luasocket' and 'warmist/advfort' into develop

develop
lethosor 2015-09-12 19:59:53 -04:00
commit a852ca10d7
6 changed files with 823 additions and 107 deletions

@ -3447,6 +3447,73 @@ Simple mechanical workshop::
} }
} }
Luasocket
=========
A way to access csocket from lua. The usage is made similar to luasocket in vanilla lua distributions. Currently
only subset of functions exist and only tcp mode is implemented.
Socket class
------------
This is a base class for ``client`` and ``server`` sockets. You can not create it - it's like a virtual
base class in c++.
* ``socket:close()``
Closes the connection.
* ``socket:setTimeout(sec,msec)``
Sets the operation timeout for this socket. It's possible to set timeout to 0. Then it performs like
a non-blocking socket.
Client class
------------
Client is a connection socket to a server. You can get this object either from ``tcp:connect(address,port)`` or
from ``server:accept()``. It's a subclass of ``socket``.
* ``client:receive(pattern)``
Receives data. If ``pattern`` is a number, it receives that much data. Other supported patterns:
* ``*a``
Read all available data.
* ``*l``
Read one line. This is the default mode (if pattern is nil).
* ``client:send(data)``
Sends data. Data is a string.
Server class
------------
Server is a socket that is waiting for clients. You can get this object from ``tcp:bind(address,port)``.
* ``server:accept()``
Accepts an incoming connection if it exists. Returns a ``client`` object representing that socket.
Tcp class
---------
A class with all the tcp functionality.
* ``tcp:bind(address,port)``
Starts listening on that port for incoming connections. Returns ``server`` object.
* ``tcp:connect(address,port)``
Tries connecting to that address and port. Returns ``client`` object.
======= =======
Scripts Scripts
======= =======

@ -135,6 +135,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(jobutils jobutils.cpp) DFHACK_PLUGIN(jobutils jobutils.cpp)
DFHACK_PLUGIN(lair lair.cpp) DFHACK_PLUGIN(lair lair.cpp)
DFHACK_PLUGIN(liquids liquids.cpp Brushes.h LINK_LIBRARIES lua) DFHACK_PLUGIN(liquids liquids.cpp Brushes.h LINK_LIBRARIES lua)
DFHACK_PLUGIN(luasocket luasocket.cpp LINK_LIBRARIES clsocket lua dfhack-tinythread)
DFHACK_PLUGIN(manipulator manipulator.cpp) DFHACK_PLUGIN(manipulator manipulator.cpp)
DFHACK_PLUGIN(mode mode.cpp) DFHACK_PLUGIN(mode mode.cpp)
#DFHACK_PLUGIN(misery misery.cpp) #DFHACK_PLUGIN(misery misery.cpp)

@ -0,0 +1,67 @@
local _ENV = mkmodule('plugins.luasocket')
local _funcs={}
for k,v in pairs(_ENV) do
if type(v)=="function" then
_funcs[k]=v
_ENV[k]=nil
end
end
local socket=defclass(socket)
socket.ATTRS={
server_id=-1,
client_id=-1,
}
function socket:close( )
if self.client_id==-1 then
_funcs.lua_server_close(self.server_id)
else
_funcs.lua_client_close(self.server_id,self.client_id)
end
end
function socket:setTimeout( sec,msec )
msec=msec or 0
_funcs.lua_socket_set_timeout(self.server_id,self.client_id,sec,msec)
end
local client=defclass(client,socket)
function client:receive( pattern )
local pattern=pattern or "*l"
local bytes=-1
if type(pattern)== number then
bytes=pattern
end
local ret=_funcs.lua_client_receive(self.server_id,self.client_id,bytes,pattern,false)
if ret=="" then
return
else
return ret
end
end
function client:send( data )
_funcs.lua_client_send(self.server_id,self.client_id,data)
end
local server=defclass(server,socket)
function server:accept()
local id=_funcs.lua_server_accept(self.server_id,false)
if id~=nil then
return client{server_id=self.server_id,client_id=id}
else
return
end
end
tcp={}
function tcp:bind( address,port )
local id=_funcs.lua_socket_bind(address,port)
return server{server_id=id}
end
function tcp:connect( address,port )
local id=_funcs.lua_socket_connect(address,port)
return client{client_id=id}
end
--TODO garbage collect stuff
return _ENV

@ -0,0 +1,352 @@
#include "Core.h"
#include "Console.h"
#include "Export.h"
#include "PluginManager.h"
#include "DataDefs.h"
#include <vector>
#include <string>
#include <map>
#include <memory>
#include <PassiveSocket.h>
#include <ActiveSocket.h>
#include "MiscUtils.h"
#include "LuaTools.h"
#include "DataFuncs.h"
#include <stdexcept> //todo convert errors to lua-errors and co. Then remove this
using namespace DFHack;
using namespace df::enums;
struct server
{
CPassiveSocket *socket;
std::map<int,CActiveSocket*> clients;
int last_client_id;
void close();
};
std::map<int,server> servers;
typedef std::map<int,CActiveSocket*> clients_map;
clients_map clients; //free clients, i.e. non-server spawned clients
DFHACK_PLUGIN("luasocket");
// The error messages are taken from the clsocket source code
const char * translate_socket_error(CSimpleSocket::CSocketError err) {
switch (err) {
case CSimpleSocket::SocketError:
return "Generic socket error translates to error below.";
case CSimpleSocket::SocketSuccess:
return "No socket error.";
case CSimpleSocket::SocketInvalidSocket:
return "Invalid socket handle.";
case CSimpleSocket::SocketInvalidAddress:
return "Invalid destination address specified.";
case CSimpleSocket::SocketInvalidPort:
return "Invalid destination port specified.";
case CSimpleSocket::SocketConnectionRefused:
return "No server is listening at remote address.";
case CSimpleSocket::SocketTimedout:
return "Timed out while attempting operation.";
case CSimpleSocket::SocketEwouldblock:
return "Operation would block if socket were blocking.";
case CSimpleSocket::SocketNotconnected:
return "Currently not connected.";
case CSimpleSocket::SocketEinprogress:
return "Socket is non-blocking and the connection cannot be completed immediately";
case CSimpleSocket::SocketInterrupted:
return "Call was interrupted by a signal that was caught before a valid connection arrived.";
case CSimpleSocket::SocketConnectionAborted:
return "The connection has been aborted.";
case CSimpleSocket::SocketProtocolError:
return "Invalid protocol for operation.";
case CSimpleSocket::SocketFirewallError:
return "Firewall rules forbid connection.";
case CSimpleSocket::SocketInvalidSocketBuffer:
return "The receive buffer point outside the process's address space.";
case CSimpleSocket::SocketConnectionReset:
return "Connection was forcibly closed by the remote host.";
case CSimpleSocket::SocketAddressInUse:
return "Address already in use.";
case CSimpleSocket::SocketInvalidPointer:
return "Pointer type supplied as argument is invalid.";
case CSimpleSocket::SocketEunknown:
return "Unknown error please report to mark@carrierlabs.com";
default:
return "No such CSimpleSocket error";
}
}
void server::close()
{
for(auto it=clients.begin();it!=clients.end();it++)
{
CActiveSocket* sock=it->second;
sock->Close();
delete sock;
}
clients.clear();
socket->Close();
delete socket;
}
std::pair<CActiveSocket*,clients_map*> get_client(int server_id,int client_id)
{
std::map<int,CActiveSocket*>* target=&clients;
if(server_id>0)
{
if(servers.count(server_id)==0)
{
throw std::runtime_error("Server with this id does not exist");
}
server &cur_server=servers[server_id];
target=&cur_server.clients;
}
if(target->count(client_id)==0)
{
throw std::runtime_error("Client does with this id not exist");
}
CActiveSocket *sock=(*target)[client_id];
return std::make_pair(sock,target);
}
void handle_error(CSimpleSocket::CSocketError err,bool skip_timeout=true)
{
if(err==CSimpleSocket::SocketSuccess)
return;
if(err==CSimpleSocket::SocketTimedout && skip_timeout)
return;
throw std::runtime_error(translate_socket_error(err));
}
static int lua_socket_bind(std::string ip,int port)
{
static int server_id=0;
CPassiveSocket* sock=new CPassiveSocket;
if(!sock->Initialize())
{
CSimpleSocket::CSocketError err=sock->GetSocketError();
delete sock;
handle_error(err,false);
}
sock->SetBlocking();
if(!sock->Listen((uint8_t*)ip.c_str(),port))
{
handle_error(sock->GetSocketError(),false);
}
server_id++;
server& cur_server=servers[server_id];
cur_server.socket=sock;
cur_server.last_client_id=0;
return server_id;
}
static int lua_server_accept(int id,bool fail_on_timeout)
{
if(servers.count(id)==0)
{
throw std::runtime_error("Server not bound");
}
server &cur_server=servers[id];
CActiveSocket* sock=cur_server.socket->Accept();
if(!sock)
{
handle_error(sock->GetSocketError(),!fail_on_timeout);
return 0;
}
else
{
cur_server.last_client_id++;
cur_server.clients[cur_server.last_client_id]=sock;
return cur_server.last_client_id;
}
}
static void lua_client_close(int server_id,int client_id)
{
auto info=get_client(server_id,client_id);
CActiveSocket *sock=info.first;
std::map<int,CActiveSocket*>* target=info.second;
target->erase(client_id);
CSimpleSocket::CSocketError err=CSimpleSocket::SocketSuccess;
if(!sock->Close())
err=sock->GetSocketError();
delete sock;
if(err!=CSimpleSocket::SocketSuccess)
{
throw std::runtime_error(translate_socket_error(err));
}
}
static void lua_server_close(int server_id)
{
if(servers.count(server_id)==0)
{
throw std::runtime_error("Server with this id does not exist");
}
server &cur_server=servers[server_id];
try{
cur_server.close();
}
catch(...)
{
servers.erase(server_id);
throw;
}
}
static std::string lua_client_receive(int server_id,int client_id,int bytes,std::string pattern,bool fail_on_timeout)
{
auto info=get_client(server_id,client_id);
CActiveSocket *sock=info.first;
if(bytes>0)
{
if(sock->Receive(bytes)<=0)
{
throw std::runtime_error(translate_socket_error(sock->GetSocketError()));
}
return std::string((char*)sock->GetData(),bytes);
}
else
{
std::string ret;
if(pattern=="*a") //??
{
while(true)
{
int received=sock->Receive(1);
if(received<0)
{
handle_error(sock->GetSocketError(),!fail_on_timeout);
return "";//maybe return partial string?
}
else if(received==0)
{
break;
}
ret+=(char)*sock->GetData();
}
return ret;
}
else if (pattern=="" || pattern=="*l")
{
while(true)
{
if(sock->Receive(1)<=0)
{
handle_error(sock->GetSocketError(),!fail_on_timeout);
return "";//maybe return partial string?
}
char rec=(char)*sock->GetData();
if(rec=='\n')
break;
ret+=rec;
}
return ret;
}
else
{
throw std::runtime_error("Unsupported receive pattern");
}
}
}
static void lua_client_send(int server_id,int client_id,std::string data)
{
if(data.size()==0)
return;
std::map<int,CActiveSocket*>* target=&clients;
if(server_id>0)
{
if(servers.count(server_id)==0)
{
throw std::runtime_error("Server with this id does not exist");
}
server &cur_server=servers[server_id];
target=&cur_server.clients;
}
if(target->count(client_id)==0)
{
throw std::runtime_error("Client does with this id not exist");
}
CActiveSocket *sock=(*target)[client_id];
if(sock->Send((const uint8_t*)data.c_str(),data.size())!=data.size())
{
throw std::runtime_error(translate_socket_error(sock->GetSocketError()));
}
}
static int lua_socket_connect(std::string ip,int port)
{
static int last_client_id=0;
CActiveSocket* sock=new CActiveSocket;
if(!sock->Initialize())
{
CSimpleSocket::CSocketError err=sock->GetSocketError();
delete sock;
throw std::runtime_error(translate_socket_error(err));
}
if(!sock->Open((const uint8_t*)ip.c_str(),port))
{
CSimpleSocket::CSocketError err=sock->GetSocketError();
delete sock;
throw std::runtime_error(translate_socket_error(err));
}
last_client_id++;
clients[last_client_id]=sock;
return last_client_id;
}
static void lua_socket_set_timeout(int server_id,int client_id,int32_t sec,int32_t msec)
{
std::map<int,CActiveSocket*>* target=&clients;
if(server_id>0)
{
if(servers.count(server_id)==0)
{
throw std::runtime_error("Server with this id does not exist");
}
server &cur_server=servers[server_id];
if(client_id==-1)
{
cur_server.socket->SetConnectTimeout(sec,msec);
cur_server.socket->SetReceiveTimeout(sec,msec);
cur_server.socket->SetSendTimeout(sec,msec);
return;
}
target=&cur_server.clients;
}
if(target->count(client_id)==0)
{
throw std::runtime_error("Client does with this id not exist");
}
CActiveSocket *sock=(*target)[client_id];
sock->SetConnectTimeout(sec,msec);
sock->SetReceiveTimeout(sec,msec);
sock->SetSendTimeout(sec,msec);
}
DFHACK_PLUGIN_LUA_FUNCTIONS {
DFHACK_LUA_FUNCTION(lua_socket_bind), //spawn a server
DFHACK_LUA_FUNCTION(lua_socket_connect),//spawn a client (i.e. connection)
DFHACK_LUA_FUNCTION(lua_socket_set_timeout),
DFHACK_LUA_FUNCTION(lua_server_accept),
DFHACK_LUA_FUNCTION(lua_server_close),
DFHACK_LUA_FUNCTION(lua_client_close),
DFHACK_LUA_FUNCTION(lua_client_send),
DFHACK_LUA_FUNCTION(lua_client_receive),
DFHACK_LUA_END
};
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
for(auto it=clients.begin();it!=clients.end();it++)
{
CActiveSocket* sock=it->second;
sock->Close();
delete sock;
}
clients.clear();
for(auto it=servers.begin();it!=servers.end();it++)
{
it->second.close();
}
servers.clear();
return CR_OK;
}

@ -1,8 +1,24 @@
-- allows to do jobs in adv. mode. -- allows to do jobs in adv. mode.
--[==[ --[==[
version: 0.012 version: 0.03
changelog: changelog:
*0.031
- make forbiding optional (-s)afe mode
*0.03
- forbid doing anything in non-sites unless you are (-c)heating
- a bit more documentation and tidying
- add a deadlock fix
*0.021
- advfort_items now autofills items
- tried out few things to fix gather plants
*0.02
- fixed axles not being able to be placed in other direction (thanks SyrusLD)
- added lever linking
- restructured advfort_items, don't forget to update that too!
- Added clutter view if shop is cluttered.
*0.013
- fixed siege weapons and traps (somewhat). Now you can load them with new menu :)
*0.012 *0.012
- fix for some jobs not finding correct building. - fix for some jobs not finding correct building.
*0.011 *0.011
@ -21,6 +37,16 @@
- kind-of fixed the item problem... now they get teleported (if teleport_items=true which should be default for adventurer) - kind-of fixed the item problem... now they get teleported (if teleport_items=true which should be default for adventurer)
- gather plants still not working... Other jobs seem to work. - gather plants still not working... Other jobs seem to work.
- added new-and-improved waiting. Interestingly it could be improved to be interuptable. - added new-and-improved waiting. Interestingly it could be improved to be interuptable.
todo list:
- document everything! Maybe somebody would understand what is happening then and help me :<
- when building trap add to known traps (or known adventurers?) so that it does not trigger on adventurer
bugs list:
- items blocking construction stuck the game
- burning charcoal crashed game
- gem thingies probably broken
- custom reactions semibroken
- gathering plants still broken
--]==] --]==]
--keybinding, change to your hearts content. Only the key part. --keybinding, change to your hearts content. Only the key part.
@ -47,6 +73,7 @@ build_filter.HUMANish={
forbid={} forbid={}
} }
--economic stone fix: just disable all of them
--[[ FIXME: maybe let player select which to disable?]] --[[ FIXME: maybe let player select which to disable?]]
for k,v in ipairs(df.global.ui.economic_stone) do df.global.ui.economic_stone[k]=0 end for k,v in ipairs(df.global.ui.economic_stone) do df.global.ui.economic_stone[k]=0 end
@ -97,14 +124,17 @@ for k,v in ipairs({...}) do --setting parsing
if v=="-c" or v=="--cheat" then if v=="-c" or v=="--cheat" then
settings.build_by_items=true settings.build_by_items=true
settings.df_assign=false settings.df_assign=false
elseif v=="-s" or v=="--safe" then
settings.safe=true
elseif v=="-i" or v=="--inventory" then elseif v=="-i" or v=="--inventory" then
settings.check_inv=true settings.check_inv=true
settings.df_assign=false settings.df_assign=false
elseif v=="-a" or v=="--nodfassign" then elseif v=="-a" or v=="--nodfassign" then
settings.df_assign=false settings.df_assign=false
elseif v=="-h" or v=="--help" then
settings.help=true
else else
mode_name=v mode_name=v
end end
end end
@ -139,6 +169,12 @@ function showHelp()
Disclaimer(helptext) Disclaimer(helptext)
dialog.showMessage("Help!?!",helptext) dialog.showMessage("Help!?!",helptext)
end end
if settings.help then
showHelp()
return
end
--[[ Util functions ]]-- --[[ Util functions ]]--
function advGlobalPos() function advGlobalPos()
local map=df.global.world.map local map=df.global.world.map
@ -152,14 +188,13 @@ function advGlobalPos()
return math.floor(map.region_x+adv.pos.x/48), math.floor(map.region_y+adv.pos.y/48) return math.floor(map.region_x+adv.pos.x/48), math.floor(map.region_y+adv.pos.y/48)
end end
function inSite() function inSite()
local tx,ty=advGlobalPos() local tx,ty=advGlobalPos()
--print(tx,ty)
for k,v in pairs(df.global.world.world_data.sites) do for k,v in pairs(df.global.world.world_data.sites) do
local tp={v.pos.x,v.pos.y} local tp={v.pos.x,v.pos.y}
if tx>=tp[1]*16+v.rgn_min_x and tx<=tp[1]*16+v.rgn_max_x and if tx>=tp[1]*16+v.rgn_min_x and tx<=tp[1]*16+v.rgn_max_x and
ty>=tp[2]*16+v.rgn_min_y and ty<=tp[2]*16+v.rgn_max_y then ty>=tp[2]*16+v.rgn_min_y and ty<=tp[2]*16+v.rgn_max_y then
--print(k)
return v return v
end end
end end
@ -285,7 +320,8 @@ function SetWebRef(args)
local pos=args.pos local pos=args.pos
for k,v in pairs(df.global.world.items.other.ANY_WEBS) do for k,v in pairs(df.global.world.items.other.ANY_WEBS) do
if v.pos.x==pos.x and v.pos.y==pos.y and v.pos.z==pos.z then if v.pos.x==pos.x and v.pos.y==pos.y and v.pos.z==pos.z then
job.general_refs:insert("#",{new=df.general_ref_item,item_id=v.id}) args.job.general_refs:insert("#",{new=df.general_ref_item,item_id=v.id})
return
end end
end end
end end
@ -460,7 +496,7 @@ function chooseBuildingWidthHeightDir(args) --TODO nicer selection dialog
local all=makeset{"w","h","d"} local all=makeset{"w","h","d"}
local needs={[btype.FarmPlot]=area,[btype.Bridge]=all, local needs={[btype.FarmPlot]=area,[btype.Bridge]=all,
[btype.RoadDirt]=area,[btype.RoadPaved]=area,[btype.ScrewPump]=makeset{"d"}, [btype.RoadDirt]=area,[btype.RoadPaved]=area,[btype.ScrewPump]=makeset{"d"},
[btype.AxleHorizontal]=makeset{"w","h"},[btype.WaterWheel]=makeset{"d"},[btype.Rollers]=makeset{"d"}} [btype.AxleHorizontal]=all,[btype.WaterWheel]=makeset{"d"},[btype.Rollers]=makeset{"d"}}
local myneeds=needs[args.type] local myneeds=needs[args.type]
if myneeds==nil then return end if myneeds==nil then return end
if args.width==nil and myneeds.w then if args.width==nil and myneeds.w then
@ -499,7 +535,6 @@ function BuildingChosen(inp_args,type_id,subtype_id,custom_id)
last_building.custom=args.custom last_building.custom=args.custom
if chooseBuildingWidthHeightDir(args) then if chooseBuildingWidthHeightDir(args) then
return return
end end
--if settings.build_by_items then --if settings.build_by_items then
@ -530,7 +565,6 @@ function isSuitableItem(job_item,item)
--todo butcher test --todo butcher test
if job_item.item_type~=-1 then if job_item.item_type~=-1 then
if item:getType()~= job_item.item_type then if item:getType()~= job_item.item_type then
return false, "type" return false, "type"
elseif job_item.item_subtype~=-1 then elseif job_item.item_subtype~=-1 then
if item:getSubtype()~=job_item.item_subtype then if item:getSubtype()~=job_item.item_subtype then
@ -558,7 +592,7 @@ function isSuitableItem(job_item,item)
--print(matinfo:getCraftClass()) --print(matinfo:getCraftClass())
--print("Matching ",item," vs ",job_item) --print("Matching ",item," vs ",job_item)
if not matinfo:matches(job_item) then if type(job_item) ~= "table" and not matinfo:matches(job_item) then
--[[ --[[
local true_flags={} local true_flags={}
for k,v in pairs(job_item.flags1) do for k,v in pairs(job_item.flags1) do
@ -595,8 +629,8 @@ function isSuitableItem(job_item,item)
end end
if job_item.min_dimension~=-1 then if job_item.min_dimension~=-1 then
end end
if #job_item.contains~=0 then -- if #job_item.contains~=0 then
end -- end
if job_item.has_tool_use~=-1 then if job_item.has_tool_use~=-1 then
if not item:hasToolUse(job_item.has_tool_use) then if not item:hasToolUse(job_item.has_tool_use) then
return false,"tool use" return false,"tool use"
@ -719,39 +753,30 @@ function finish_item_assign(args)
uncollected[1].is_fetching=1 uncollected[1].is_fetching=1
end end
end end
function AssignJobItems(args) function EnumItems_with_settings( args )
print("----")
if settings.df_assign then --use df default logic and hope that it would work
return true
end
-- first find items that you want to use for the job
local job=args.job
local its
if settings.check_inv then if settings.check_inv then
its=EnumItems{pos=args.from_pos,unit=args.unit, return EnumItems{pos=args.from_pos,unit=args.unit,
inv={[df.unit_inventory_item.T_mode.Hauled]=settings.use_worn,[df.unit_inventory_item.T_mode.Worn]=settings.use_worn, inv={[df.unit_inventory_item.T_mode.Hauled]=settings.use_worn,[df.unit_inventory_item.T_mode.Worn]=settings.use_worn,
[df.unit_inventory_item.T_mode.Weapon]=settings.use_worn,},deep=true} [df.unit_inventory_item.T_mode.Weapon]=settings.use_worn,},deep=true}
else else
its=EnumItems{pos=args.from_pos} return EnumItems{pos=args.from_pos}
end end
end
--[[while(#job.items>0) do --clear old job items function find_suitable_items(job,items,job_items)
job.items[#job.items-1]:delete() job_items=job_items or job.job_items
job.items:erase(#job.items-1)
end]]
local item_counts={} local item_counts={}
for job_id, trg_job_item in ipairs(job.job_items) do for job_id, trg_job_item in ipairs(job_items) do
item_counts[job_id]=trg_job_item.quantity item_counts[job_id]=trg_job_item.quantity
end end
local item_suitability={} local item_suitability={}
local used_item_id={} local used_item_id={}
for job_id, trg_job_item in ipairs(job.job_items) do for job_id, trg_job_item in ipairs(job_items) do
item_suitability[job_id]={} item_suitability[job_id]={}
for _,cur_item in pairs(its) do for _,cur_item in pairs(items) do
if not used_item_id[cur_item.id] then if not used_item_id[cur_item.id] then
local item_suitable,msg=isSuitableItem(trg_job_item,cur_item) local item_suitable,msg=isSuitableItem(trg_job_item,cur_item)
if item_suitable or settings.build_by_items then if item_suitable or settings.build_by_items then
table.insert(item_suitability[job_id],cur_item) table.insert(item_suitability[job_id],cur_item)
@ -773,21 +798,32 @@ function AssignJobItems(args)
end end
end end
end end
print("before block")
return item_suitability,item_counts
end
function AssignJobItems(args)
if settings.df_assign then --use df default logic and hope that it would work
return true
end
-- first find items that you want to use for the job
local job=args.job
local its=EnumItems_with_settings(args)
local item_suitability,item_counts=find_suitable_items(job,its)
--[[while(#job.items>0) do --clear old job items
job.items[#job.items-1]:delete()
job.items:erase(#job.items-1)
end]]
if settings.gui_item_select and #job.job_items>0 then if settings.gui_item_select and #job.job_items>0 then
local item_dialog=require('hack.scripts.gui.advfort_items') local item_dialog=require('hack.scripts.gui.advfort_items')
--local rr=require('gui.script').start(function()
print("before dialog")
local ret=item_dialog.showItemEditor(job,item_suitability) local ret=item_dialog.showItemEditor(job,item_suitability)
print("post dialog",ret)
--showItemEditor(job,item_suitability)
if ret then if ret then
finish_item_assign(args) finish_item_assign(args)
return true return true
else else
print("Failed job, i'm confused...") print("Failed job, i'm confused...")
end end
--end) --end)
return false,"Selecting items" return false,"Selecting items"
else else
@ -803,8 +839,6 @@ function AssignJobItems(args)
return true return true
end end
end end
CheckAndFinishBuilding=function (args,bld) CheckAndFinishBuilding=function (args,bld)
@ -877,7 +911,125 @@ function ContinueJob(unit)
--unit.path.dest:assign(c_job.pos) -- FIXME: job pos is not always the target pos!! --unit.path.dest:assign(c_job.pos) -- FIXME: job pos is not always the target pos!!
addJobAction(c_job,unit) addJobAction(c_job,unit)
end end
--TODO: in far far future maybe add real linking?
-- function assign_link_refs(args )
-- local job=args.job
-- --job.general_refs:insert("#",{new=df.general_ref_building_holderst,building_id=args.building.id})
-- job.general_refs:insert("#",{new=df.general_ref_building_triggertargetst,building_id=args.triggertarget.id})
-- printall(job)
-- end
-- function assign_link_roles( args )
-- if #args.job.items~=2 then
-- print("AAA FAILED!")
-- return false
-- end
-- args.job.items[0].role=df.job_item_ref.T_role.LinkToTarget
-- args.job.items[1].role=df.job_item_ref.T_role.LinkToTrigger
-- end
function fake_linking(lever,building,slots)
local item1=slots[1].items[1]
local item2=slots[2].items[1]
if not dfhack.items.moveToBuilding(item1,lever,2) then
qerror("failed to move item to building")
end
if not dfhack.items.moveToBuilding(item2,building,2) then
qerror("failed to move item2 to building")
end
item2.general_refs:insert("#",{new=df.general_ref_building_triggerst,building_id=lever.id})
item1.general_refs:insert("#",{new=df.general_ref_building_triggertargetst,building_id=building.id})
lever.linked_mechanisms:insert("#",item2)
--fixes...
if building:getType()==df.building_type.Door then
building.door_flags.operated_by_mechanisms=true
end
dfhack.gui.showAnnouncement("Linked!",COLOR_YELLOW,true)
end
function LinkBuilding(args)
local bld=args.building or dfhack.buildings.findAtTile(args.pos)
args.building=bld
local lever_bld
if lever_id then --intentionally global!
lever_bld=df.building.find(lever_id)
if lever_bld==nil then
lever_id=nil
end
end
if lever_bld==nil then
if bld:getType()==df.building_type.Trap and bld:getSubtype()==df.trap_type.Lever then
lever_id=bld.id
dfhack.gui.showAnnouncement("Selected lever for linking",COLOR_YELLOW,true)
return
else
dfhack.gui.showAnnouncement("You first need a lever",COLOR_RED,true)
end
else
if lever_bld==bld then
dfhack.gui.showAnnouncement("Invalid target",COLOR_RED,true) --todo more invalid targets
return
end
-- args.job_type=df.job_type.LinkBuildingToTrigger
-- args.building=lever_bld
-- args.triggertarget=bld
-- args.pre_actions={
-- dfhack.curry(setFiltersUp,{items={{quantity=1,item_type=df.item_type.TRAPPARTS},{quantity=1,item_type=df.item_type.TRAPPARTS}}}),
-- AssignJobItems,
-- assign_link_refs,}
-- args.post_actions={AssignBuildingRef,assign_link_roles}
-- makeJob(args)
local input_filter_defaults = { --stolen from buildings lua to better customize...
item_type = df.item_type.TRAPPARTS,
item_subtype = -1,
mat_type = -1,
mat_index = -1,
flags1 = {},
flags2 = { allow_artifact = true },
flags3 = {},
flags4 = 0,
flags5 = 0,
reaction_class = '',
has_material_reaction_product = '',
metal_ore = -1,
min_dimension = -1,
has_tool_use = -1,
quantity = 1
}
local job_items={copyall(input_filter_defaults),copyall(input_filter_defaults)}
local its=EnumItems_with_settings(args)
local suitability=find_suitable_items(nil,its,job_items)
require('hack.scripts.gui.advfort_items').jobitemEditor{items=suitability,job_items=job_items,on_okay=dfhack.curry(fake_linking,lever_bld,bld)}:show()
lever_id=nil
end
--one item as LinkToTrigger role
--one item as LinkToTarget
--genref for holder(lever)
--genref for triggertarget
end
--[[ Plant gathering attemped fix No. 35]] --[=[ still did not work!]=]
function get_design_block_ev(blk)
for i,v in ipairs(blk.block_events) do
if v:getType()==df.block_square_event_type.designation_priority then
return v
end
end
end
function PlantGatherFix(args)
args.job.flags[17]=true --??
local pos=args.pos
local block=dfhack.maps.getTileBlock(pos)
local ev=get_design_block_ev(block)
if ev==nil then
block.block_events:insert("#",{new=df.block_square_event_designation_priorityst})
ev=block.block_events[#block.block_events-1]
end
ev.priority[pos.x % 16][pos.y % 16]=bit32.bor(ev.priority[pos.x % 16][pos.y % 16],4000)
args.job.item_category:assign{furniture=true,corpses=true,ammo=true} --this is actually required in fort mode
end
actions={ actions={
{"CarveFortification" ,df.job_type.CarveFortification,{IsWall,IsHardMaterial}}, {"CarveFortification" ,df.job_type.CarveFortification,{IsWall,IsHardMaterial}},
{"DetailWall" ,df.job_type.DetailWall,{IsWall,IsHardMaterial}}, {"DetailWall" ,df.job_type.DetailWall,{IsWall,IsHardMaterial}},
@ -895,7 +1047,7 @@ actions={
--{"Diagnose Patient" ,df.job_type.DiagnosePatient,{IsUnit},{SetPatientRef}}, --{"Diagnose Patient" ,df.job_type.DiagnosePatient,{IsUnit},{SetPatientRef}},
--{"Surgery" ,df.job_type.Surgery,{IsUnit},{SetPatientRef}}, --{"Surgery" ,df.job_type.Surgery,{IsUnit},{SetPatientRef}},
{"TameAnimal" ,df.job_type.TameAnimal,{IsUnit},{SetCreatureRef}}, {"TameAnimal" ,df.job_type.TameAnimal,{IsUnit},{SetCreatureRef}},
{"GatherPlants" ,df.job_type.GatherPlants,{IsPlant,SameSquare}}, {"GatherPlants" ,df.job_type.GatherPlants,{IsPlant,SameSquare},{PlantGatherFix}},
{"RemoveConstruction" ,df.job_type.RemoveConstruction,{IsConstruct}}, {"RemoveConstruction" ,df.job_type.RemoveConstruction,{IsConstruct}},
{"RemoveBuilding" ,RemoveBuilding,{IsBuilding}}, {"RemoveBuilding" ,RemoveBuilding,{IsBuilding}},
{"RemoveStairs" ,df.job_type.RemoveStairs,{IsStairs,NotConstruct}}, {"RemoveStairs" ,df.job_type.RemoveStairs,{IsStairs,NotConstruct}},
@ -904,7 +1056,7 @@ actions={
{"BuildLast" ,BuildLast,{NoConstructedBuilding}}, {"BuildLast" ,BuildLast,{NoConstructedBuilding}},
{"Clean" ,df.job_type.Clean,{}}, {"Clean" ,df.job_type.Clean,{}},
{"GatherWebs" ,df.job_type.CollectWebs,{--[[HasWeb]]},{SetWebRef}}, {"GatherWebs" ,df.job_type.CollectWebs,{--[[HasWeb]]},{SetWebRef}},
{"Link Buildings" ,LinkBuilding,{IsBuilding}},
} }
for id,action in pairs(actions) do for id,action in pairs(actions) do
@ -925,6 +1077,22 @@ function usetool:getModeName()
end end
function usetool:update_site()
local site=inSite()
self.current_site=site
local site_label=self.subviews.siteLabel
if site then
site_label:itemById("site").text=dfhack.TranslateName(site.name)
else
if settings.safe then
site_label:itemById("site").text="<none, advfort disabled>"
else
site_label:itemById("site").text="<none, changes will not persist>"
end
end
end
function usetool:init(args) function usetool:init(args)
self:addviews{ self:addviews{
wid.Label{ wid.Label{
@ -934,19 +1102,17 @@ function usetool:init(args)
} }
}, },
wid.Label{ wid.Label{
view_id="shopLabel", view_id="shopLabel",
frame = {l=35,xalign=0,yalign=0}, frame = {l=35,xalign=0,yalign=0},
visible=false, visible=false,
text={ text={
{id="text1",gap=1,key=keybinds.workshop.key,key_sep="()", text="Workshop menu",pen=dfhack.pen.parse{fg=COLOR_YELLOW,bg=0}}} {id="text1",gap=1,key=keybinds.workshop.key,key_sep="()", text="Workshop menu",pen=dfhack.pen.parse{fg=COLOR_YELLOW,bg=0}},{id="clutter"}}
}, },
wid.Label{ wid.Label{
view_id="siteLabel", view_id="siteLabel",
frame = {t=1,xalign=-1,yalign=0}, frame = {t=1,xalign=-1,yalign=0},
visible=false,
text={ text={
{id="text1", text="Site:"},{id="site", text="name"} {id="text1", text="Site:"},{id="site", text="name"}
} }
@ -956,6 +1122,7 @@ function usetool:init(args)
for i,v in ipairs(labors) do for i,v in ipairs(labors) do
labors[i]=true labors[i]=true
end end
self:update_site()
end end
MOVEMENT_KEYS = { MOVEMENT_KEYS = {
A_CARE_MOVE_N = { 0, -1, 0 }, A_CARE_MOVE_S = { 0, 1, 0 }, A_CARE_MOVE_N = { 0, -1, 0 }, A_CARE_MOVE_S = { 0, 1, 0 },
@ -1007,40 +1174,37 @@ function onWorkShopJobChosen(args,idx,choice)
args.pre_actions={dfhack.curry(setFiltersUp,choice.filter),AssignJobItems} args.pre_actions={dfhack.curry(setFiltersUp,choice.filter),AssignJobItems}
makeJob(args) makeJob(args)
end end
function siegeWeaponActionChosen(building,actionid) function siegeWeaponActionChosen(args,actionid)
local args local building=args.building
if actionid==1 then if actionid==1 then --Turn
building.facing=(building.facing+1)%4 building.facing=(args.building.facing+1)%4
elseif actionid==2 then return
elseif actionid==2 then --Load
local action=df.job_type.LoadBallista local action=df.job_type.LoadBallista
if building:getSubtype()==df.siegeengine_type.Catapult then if building:getSubtype()==df.siegeengine_type.Catapult then
action=df.job_type.LoadCatapult action=df.job_type.LoadCatapult
args.pre_actions={dfhack.curry(setFiltersUp,{items={{quantity=1}}}),AssignJobItems} --TODO just boulders here
else
args.pre_actions={dfhack.curry(setFiltersUp,{items={{quantity=1,item_type=df.SIEGEAMMO}}}),AssignJobItems}
end end
args={}
args.job_type=action args.job_type=action
args.unit=df.global.world.units.active[0] args.unit=df.global.world.units.active[0]
local from_pos={x=args.unit.pos.x,y=args.unit.pos.y, z=args.unit.pos.z} local from_pos={x=args.unit.pos.x,y=args.unit.pos.y, z=args.unit.pos.z}
args.from_pos=from_pos args.from_pos=from_pos
args.pos=from_pos args.pos=from_pos
args.pre_actions={dfhack.curry(setFiltersUp,{items={{}}})} elseif actionid==3 then --Fire
--issue a job...
elseif actionid==3 then
local action=df.job_type.FireBallista local action=df.job_type.FireBallista
if building:getSubtype()==df.siegeengine_type.Catapult then if building:getSubtype()==df.siegeengine_type.Catapult then
action=df.job_type.FireCatapult action=df.job_type.FireCatapult
end end
args={}
args.job_type=action args.job_type=action
args.unit=df.global.world.units.active[0] args.unit=df.global.world.units.active[0]
local from_pos={x=args.unit.pos.x,y=args.unit.pos.y, z=args.unit.pos.z} local from_pos={x=args.unit.pos.x,y=args.unit.pos.y, z=args.unit.pos.z}
args.from_pos=from_pos args.from_pos=from_pos
args.pos=from_pos args.pos=from_pos
--another job?
end
if args~=nil then
args.post_actions={AssignBuildingRef}
makeJob(args)
end end
args.post_actions={AssignBuildingRef}
makeJob(args)
end end
function putItemToBuilding(building,item) function putItemToBuilding(building,item)
if building:getType()==df.building_type.Table then if building:getType()==df.building_type.Table then
@ -1051,7 +1215,6 @@ function putItemToBuilding(building,item)
end end
end end
function usetool:openPutWindow(building) function usetool:openPutWindow(building)
local adv=df.global.world.units.active[0] local adv=df.global.world.units.active[0]
local items=EnumItems{pos=adv.pos,unit=adv, local items=EnumItems{pos=adv.pos,unit=adv,
inv={[df.unit_inventory_item.T_mode.Hauled]=true,--[df.unit_inventory_item.T_mode.Worn]=true, inv={[df.unit_inventory_item.T_mode.Hauled]=true,--[df.unit_inventory_item.T_mode.Worn]=true,
@ -1063,16 +1226,15 @@ function usetool:openPutWindow(building)
dialog.showListPrompt("Item choice", "Choose item to put into:", COLOR_WHITE,choices,function (idx,choice) putItemToBuilding(building,choice.item) end) dialog.showListPrompt("Item choice", "Choose item to put into:", COLOR_WHITE,choices,function (idx,choice) putItemToBuilding(building,choice.item) end)
end end
function usetool:openSiegeWindow(building) function usetool:openSiegeWindow(building)
local args={building=building,screen=self}
dialog.showListPrompt("Engine job choice", "Choose what to do:",COLOR_WHITE,{"Turn","Load","Fire"}, dialog.showListPrompt("Engine job choice", "Choose what to do:",COLOR_WHITE,{"Turn","Load","Fire"},
dfhack.curry(siegeWeaponActionChosen,building)) dfhack.curry(siegeWeaponActionChosen,args))
end end
function usetool:onWorkShopButtonClicked(building,index,choice) function usetool:onWorkShopButtonClicked(building,index,choice)
local adv=df.global.world.units.active[0] local adv=df.global.world.units.active[0]
local args={unit=adv,building=building} local args={unit=adv,building=building}
if df.interface_button_building_new_jobst:is_instance(choice.button) then if df.interface_button_building_new_jobst:is_instance(choice.button) then
print("pre-click")
choice.button:click() choice.button:click()
print("post-click",#building.jobs)
if #building.jobs>0 then if #building.jobs>0 then
local job=building.jobs[#building.jobs-1] local job=building.jobs[#building.jobs-1]
args.job=job args.job=job
@ -1159,17 +1321,17 @@ function usetool:armCleanTrap(building)
end end
--building.trap_type==df.trap_type.PressurePlate then --building.trap_type==df.trap_type.PressurePlate then
--settings/link --settings/link
local args={unit=adv,post_actions={AssignBuildingRef,AssignJobItems},pos=adv.pos,from_pos=adv.pos, local args={unit=adv,post_actions={AssignBuildingRef},pos=adv.pos,from_pos=adv.pos,
building=building,job_type=df.job_type.CleanTrap} building=building,job_type=df.job_type.CleanTrap}
if building.trap_type==df.trap_type.CageTrap then if building.trap_type==df.trap_type.CageTrap then
args.job_type=df.job_type.LoadCageTrap args.job_type=df.job_type.LoadCageTrap
local job_filter={items={{quantity=1,item_type=df.item_type.CAGE}} } local job_filter={items={{quantity=1,item_type=df.item_type.CAGE}} }
args.pre_actions={dfhack.curry(setFiltersUp,job_filter)} args.pre_actions={dfhack.curry(setFiltersUp,job_filter),AssignJobItems}
elseif building.trap_type==df.trap_type.StoneFallTrap then elseif building.trap_type==df.trap_type.StoneFallTrap then
args.job_type=df.job_type.LoadStoneTrap args.job_type=df.job_type.LoadStoneTrap
local job_filter={items={{quantity=1,item_type=df.item_type.BOULDER}} } local job_filter={items={{quantity=1,item_type=df.item_type.BOULDER}} }
args.pre_actions={dfhack.curry(setFiltersUp,job_filter)} args.pre_actions={dfhack.curry(setFiltersUp,job_filter),AssignJobItems}
elseif building.trap_type==df.trap_type.WeaponTrap then elseif building.trap_type==df.trap_type.WeaponTrap then
qerror("TODO") qerror("TODO")
else else
@ -1181,10 +1343,10 @@ function usetool:armCleanTrap(building)
end end
function usetool:hiveActions(building) function usetool:hiveActions(building)
local adv=df.global.world.units.active[0] local adv=df.global.world.units.active[0]
local args={unit=adv,post_actions={AssignBuildingRef,AssignJobItems},pos=adv.pos, local args={unit=adv,post_actions={AssignBuildingRef},pos=adv.pos,
from_pos=adv.pos,job_type=df.job_type.InstallColonyInHive,building=building,screen=self} from_pos=adv.pos,job_type=df.job_type.InstallColonyInHive,building=building,screen=self}
local job_filter={items={{quantity=1,item_type=df.item_type.VERMIN}} } local job_filter={items={{quantity=1,item_type=df.item_type.VERMIN}} }
args.pre_actions={dfhack.curry(setFiltersUp,job_filter)} args.pre_actions={dfhack.curry(setFiltersUp,job_filter),AssignJobItems}
makeJob(args) makeJob(args)
--InstallColonyInHive, --InstallColonyInHive,
--CollectHiveProducts, --CollectHiveProducts,
@ -1298,6 +1460,11 @@ function usetool:shopMode(enable,mode,building)
self.subviews.shopLabel.visible=enable self.subviews.shopLabel.visible=enable
if mode then if mode then
self.subviews.shopLabel:itemById("text1").text=mode.name self.subviews.shopLabel:itemById("text1").text=mode.name
if building:getClutterLevel()<=1 then
self.subviews.shopLabel:itemById("clutter").text=""
else
self.subviews.shopLabel:itemById("clutter").text=" Clutter:"..tostring(building:getClutterLevel())
end
self.building=building self.building=building
end end
self.mode=mode self.mode=mode
@ -1333,6 +1500,13 @@ function usetool:setupFields()
ui.site_id=site.id ui.site_id=site.id
end end
end end
function usetool:siteCheck()
if self.site ~= nil or not settings.safe then --TODO: add check if it's correct site (the persistant ones)
return true
end
return false, "You are not on site"
end
--movement and co... Also passes on allowed keys
function usetool:fieldInput(keys) function usetool:fieldInput(keys)
local adv=df.global.world.units.active[0] local adv=df.global.world.units.active[0]
local cur_mode=actions[(mode or 0)+1] local cur_mode=actions[(mode or 0)+1]
@ -1340,9 +1514,18 @@ function usetool:fieldInput(keys)
for code,_ in pairs(keys) do for code,_ in pairs(keys) do
--print(code) --print(code)
if MOVEMENT_KEYS[code] then if MOVEMENT_KEYS[code] then
local state={unit=adv,pos=moddedpos(adv.pos,MOVEMENT_KEYS[code]),dir=MOVEMENT_KEYS[code],
from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z},post_actions=cur_mode[4],pre_actions=cur_mode[5],job_type=cur_mode[2],screen=self} local state={
if code=="SELECT" then unit=adv,
pos=moddedpos(adv.pos,MOVEMENT_KEYS[code]),
dir=MOVEMENT_KEYS[code],
from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z},
post_actions=cur_mode[4],
pre_actions=cur_mode[5],
job_type=cur_mode[2],
screen=self}
if code=="SELECT" then --do job in the distance, TODO: check if you can still cheat-mine (and co.) remotely
if df.global.cursor.x~=-30000 then if df.global.cursor.x~=-30000 then
state.pos={x=df.global.cursor.x,y=df.global.cursor.y,z=df.global.cursor.z} state.pos={x=df.global.cursor.x,y=df.global.cursor.y,z=df.global.cursor.z}
else else
@ -1350,11 +1533,18 @@ function usetool:fieldInput(keys)
end end
end end
for _,p in pairs(cur_mode[3] or {}) do --First check site
local ok,msg=p(state) local ok,msg=self:siteCheck() --TODO: some jobs might be possible without a site?
if ok==false then if not ok then
dfhack.gui.showAnnouncement(msg,5,1) dfhack.gui.showAnnouncement(msg,5,1)
failed=true failed=true
else
for _,p in pairs(cur_mode[3] or {}) do --then check predicates
local ok,msg=p(state)
if not ok then
dfhack.gui.showAnnouncement(msg,5,1)
failed=true
end
end end
end end
@ -1383,23 +1573,25 @@ function usetool:fieldInput(keys)
end end
end end
function usetool:onInput(keys) function usetool:onInput(keys)
self:update_site()
local adv=df.global.world.units.active[0] local adv=df.global.world.units.active[0]
if keys.LEAVESCREEN then if keys.LEAVESCREEN then
if df.global.cursor.x~=-30000 then if df.global.cursor.x~=-30000 then --if not poiting at anything
self:sendInputToParent("LEAVESCREEN") self:sendInputToParent("LEAVESCREEN") --leave poiting
else else
self:dismiss() self:dismiss() --leave the adv-tools all together
CancelJob(adv) CancelJob(adv)
end end
elseif keys[keybinds.nextJob.key] then elseif keys[keybinds.nextJob.key] then --next job with looping
mode=(mode+1)%#actions mode=(mode+1)%#actions
elseif keys[keybinds.prevJob.key] then elseif keys[keybinds.prevJob.key] then --prev job with looping
mode=mode-1 mode=mode-1
if mode<0 then mode=#actions-1 end if mode<0 then mode=#actions-1 end
--elseif keys.A_LOOK then
-- self:sendInputToParent("A_LOOK")
elseif keys["A_SHORT_WAIT"] then elseif keys["A_SHORT_WAIT"] then
--ContinueJob(adv) --ContinueJob(adv)
self:sendInputToParent("A_SHORT_WAIT") self:sendInputToParent("A_SHORT_WAIT")
@ -1417,14 +1609,7 @@ function usetool:onInput(keys)
self:fieldInput(keys) self:fieldInput(keys)
end end
end end
local site=inSite()
if site then
self.subviews.siteLabel.visible=true
self.subviews.siteLabel:itemById("site").text=dfhack.TranslateName(site.name)
else
self.subviews.siteLabel.visible=false
end
end end
function usetool:onIdle() function usetool:onIdle()
@ -1432,7 +1617,19 @@ function usetool:onIdle()
local job_ptr=adv.job.current_job local job_ptr=adv.job.current_job
local job_action=findAction(adv,df.unit_action_type.Job) local job_action=findAction(adv,df.unit_action_type.Job)
if self.long_wait and self.long_wait_timer==nil then
self.long_wait_timer=1000 --TODO tweak this
end
if job_ptr and self.long_wait and not job_action then if job_ptr and self.long_wait and not job_action then
if self.long_wait_timer<=0 then --fix deadlocks with force-canceling of waiting
self.long_wait_timer=nil
self.long_wait=false
else
self.long_wait_timer=self.long_wait_timer-1
end
if adv.job.current_job.completion_timer==-1 then if adv.job.current_job.completion_timer==-1 then
self.long_wait=false self.long_wait=false
end end

@ -12,8 +12,10 @@ jobitemEditor.ATTRS{
allow_remove=false, allow_remove=false,
allow_any_item=false, allow_any_item=false,
job=DEFAULT_NIL, job=DEFAULT_NIL,
job_items=DEFAULT_NIL,
items=DEFAULT_NIL, items=DEFAULT_NIL,
on_okay=DEFAULT_NIL, on_okay=DEFAULT_NIL,
autofill=true,
} }
function update_slot_text(slot) function update_slot_text(slot)
local items="" local items=""
@ -29,7 +31,7 @@ end
--items-> table => key-> id of job.job_items, value-> table => key (num), value => item(ref) --items-> table => key-> id of job.job_items, value-> table => key (num), value => item(ref)
function jobitemEditor:init(args) function jobitemEditor:init(args)
--self.job=args.job --self.job=args.job
if self.job==nil then qerror("This screen must have job target") end if self.job==nil and self.job_items==nil then qerror("This screen must have job target or job_items list") end
if self.items==nil then qerror("This screen must have item list") end if self.items==nil then qerror("This screen must have item list") end
self:addviews{ self:addviews{
@ -74,6 +76,9 @@ function jobitemEditor:init(args)
} }
self.assigned={} self.assigned={}
self:fill() self:fill()
if self.autofill then
self:fill_slots()
end
end end
function jobitemEditor:get_slot() function jobitemEditor:get_slot()
local idx,choice=self.subviews.itemList:getSelected() local idx,choice=self.subviews.itemList:getSelected()
@ -104,6 +109,23 @@ function jobitemEditor:add_item()
end end
) )
end end
function jobitemEditor:fill_slots()
for i,v in ipairs(self.slots) do
while v.filled_amount<v.job_item.quantity do
local added=false
for _,it in ipairs(v.choices) do
if not self.assigned[it.id] then
self:add_item_to_slot(v,it)
added=true
break
end
end
if not added then
break
end
end
end
end
function jobitemEditor:add_item_to_slot(slot,item) function jobitemEditor:add_item_to_slot(slot,item)
table.insert(slot.items,item) table.insert(slot.items,item)
slot.filled_amount=slot.filled_amount+item:getTotalDimension() slot.filled_amount=slot.filled_amount+item:getTotalDimension()
@ -124,7 +146,15 @@ end
function jobitemEditor:fill() function jobitemEditor:fill()
self.slots={} self.slots={}
for k,v in pairs(self.items) do for k,v in pairs(self.items) do
table.insert(self.slots,{job_item=self.job.job_items[k], id=k, items={},choices=v,filled_amount=0,slot_id=#self.slots}) local job_item
if self.job then
job_item=self.job.job_items[k]
else
job_item=self.job_items[k]
end
table.insert(self.slots,{job_item=job_item, id=k, items={},choices=v,filled_amount=0,slot_id=#self.slots})
update_slot_text(self.slots[#self.slots]) update_slot_text(self.slots[#self.slots])
end end
self.subviews.itemList:setChoices(self.slots) self.subviews.itemList:setChoices(self.slots)
@ -139,13 +169,15 @@ function jobitemEditor:jobValid()
return true return true
end end
function jobitemEditor:commit() function jobitemEditor:commit()
for _,slot in pairs(self.slots) do if self.job then
for _1,cur_item in pairs(slot.items) do for _,slot in pairs(self.slots) do
self.job.items:insert("#",{new=true,item=cur_item,role=df.job_item_ref.T_role.Reagent,job_item_idx=slot.id}) for _1,cur_item in pairs(slot.items) do
self.job.items:insert("#",{new=true,item=cur_item,role=df.job_item_ref.T_role.Reagent,job_item_idx=slot.id})
end
end end
end end
self:dismiss() self:dismiss()
if self.on_okay then self:on_okay() end if self.on_okay then self.on_okay(self.slots) end
end end
function showItemEditor(job,item_selections) function showItemEditor(job,item_selections)