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
=======

@ -135,6 +135,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(jobutils jobutils.cpp)
DFHACK_PLUGIN(lair lair.cpp)
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(mode mode.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.
--[==[
version: 0.012
version: 0.03
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
- fix for some jobs not finding correct building.
*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)
- gather plants still not working... Other jobs seem to work.
- 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.
@ -47,6 +73,7 @@ build_filter.HUMANish={
forbid={}
}
--economic stone fix: just disable all of them
--[[ 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
@ -97,14 +124,17 @@ for k,v in ipairs({...}) do --setting parsing
if v=="-c" or v=="--cheat" then
settings.build_by_items=true
settings.df_assign=false
elseif v=="-s" or v=="--safe" then
settings.safe=true
elseif v=="-i" or v=="--inventory" then
settings.check_inv=true
settings.df_assign=false
elseif v=="-a" or v=="--nodfassign" then
settings.df_assign=false
elseif v=="-h" or v=="--help" then
settings.help=true
else
mode_name=v
end
end
@ -139,6 +169,12 @@ function showHelp()
Disclaimer(helptext)
dialog.showMessage("Help!?!",helptext)
end
if settings.help then
showHelp()
return
end
--[[ Util functions ]]--
function advGlobalPos()
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)
end
function inSite()
local tx,ty=advGlobalPos()
--print(tx,ty)
for k,v in pairs(df.global.world.world_data.sites) do
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
ty>=tp[2]*16+v.rgn_min_y and ty<=tp[2]*16+v.rgn_max_y then
--print(k)
return v
end
end
@ -285,7 +320,8 @@ function SetWebRef(args)
local pos=args.pos
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
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
@ -460,7 +496,7 @@ function chooseBuildingWidthHeightDir(args) --TODO nicer selection dialog
local all=makeset{"w","h","d"}
local needs={[btype.FarmPlot]=area,[btype.Bridge]=all,
[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]
if myneeds==nil then return end
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
if chooseBuildingWidthHeightDir(args) then
return
end
--if settings.build_by_items then
@ -530,7 +565,6 @@ function isSuitableItem(job_item,item)
--todo butcher test
if job_item.item_type~=-1 then
if item:getType()~= job_item.item_type then
return false, "type"
elseif job_item.item_subtype~=-1 then
if item:getSubtype()~=job_item.item_subtype then
@ -558,7 +592,7 @@ function isSuitableItem(job_item,item)
--print(matinfo:getCraftClass())
--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={}
for k,v in pairs(job_item.flags1) do
@ -580,7 +614,7 @@ function isSuitableItem(job_item,item)
print(v)
end
--]]
return false,"matinfo"
end
-- some bonus checks:
@ -595,8 +629,8 @@ function isSuitableItem(job_item,item)
end
if job_item.min_dimension~=-1 then
end
if #job_item.contains~=0 then
end
-- if #job_item.contains~=0 then
-- end
if job_item.has_tool_use~=-1 then
if not item:hasToolUse(job_item.has_tool_use) then
return false,"tool use"
@ -719,39 +753,30 @@ function finish_item_assign(args)
uncollected[1].is_fetching=1
end
end
function AssignJobItems(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
function EnumItems_with_settings( args )
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,
[df.unit_inventory_item.T_mode.Weapon]=settings.use_worn,},deep=true}
else
its=EnumItems{pos=args.from_pos}
return EnumItems{pos=args.from_pos}
end
--[[while(#job.items>0) do --clear old job items
job.items[#job.items-1]:delete()
job.items:erase(#job.items-1)
end]]
end
function find_suitable_items(job,items,job_items)
job_items=job_items or job.job_items
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
end
local item_suitability={}
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]={}
for _,cur_item in pairs(its) do
for _,cur_item in pairs(items) do
if not used_item_id[cur_item.id] then
local item_suitable,msg=isSuitableItem(trg_job_item,cur_item)
if item_suitable or settings.build_by_items then
table.insert(item_suitability[job_id],cur_item)
@ -773,21 +798,32 @@ function AssignJobItems(args)
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
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)
print("post dialog",ret)
--showItemEditor(job,item_suitability)
if ret then
finish_item_assign(args)
return true
else
print("Failed job, i'm confused...")
end
--end)
return false,"Selecting items"
else
@ -803,8 +839,6 @@ function AssignJobItems(args)
return true
end
end
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!!
addJobAction(c_job,unit)
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={
{"CarveFortification" ,df.job_type.CarveFortification,{IsWall,IsHardMaterial}},
{"DetailWall" ,df.job_type.DetailWall,{IsWall,IsHardMaterial}},
@ -895,7 +1047,7 @@ actions={
--{"Diagnose Patient" ,df.job_type.DiagnosePatient,{IsUnit},{SetPatientRef}},
--{"Surgery" ,df.job_type.Surgery,{IsUnit},{SetPatientRef}},
{"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}},
{"RemoveBuilding" ,RemoveBuilding,{IsBuilding}},
{"RemoveStairs" ,df.job_type.RemoveStairs,{IsStairs,NotConstruct}},
@ -904,7 +1056,7 @@ actions={
{"BuildLast" ,BuildLast,{NoConstructedBuilding}},
{"Clean" ,df.job_type.Clean,{}},
{"GatherWebs" ,df.job_type.CollectWebs,{--[[HasWeb]]},{SetWebRef}},
{"Link Buildings" ,LinkBuilding,{IsBuilding}},
}
for id,action in pairs(actions) do
@ -922,7 +1074,23 @@ function usetool:getModeName()
else
return actions[(mode or 0)+1][1] or " "
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)
@ -934,19 +1102,17 @@ function usetool:init(args)
}
},
wid.Label{
view_id="shopLabel",
frame = {l=35,xalign=0,yalign=0},
visible=false,
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{
view_id="siteLabel",
frame = {t=1,xalign=-1,yalign=0},
visible=false,
text={
{id="text1", text="Site:"},{id="site", text="name"}
}
@ -956,6 +1122,7 @@ function usetool:init(args)
for i,v in ipairs(labors) do
labors[i]=true
end
self:update_site()
end
MOVEMENT_KEYS = {
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}
makeJob(args)
end
function siegeWeaponActionChosen(building,actionid)
local args
if actionid==1 then
building.facing=(building.facing+1)%4
elseif actionid==2 then
function siegeWeaponActionChosen(args,actionid)
local building=args.building
if actionid==1 then --Turn
building.facing=(args.building.facing+1)%4
return
elseif actionid==2 then --Load
local action=df.job_type.LoadBallista
if building:getSubtype()==df.siegeengine_type.Catapult then
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
args={}
args.job_type=action
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}
args.from_pos=from_pos
args.pos=from_pos
args.pre_actions={dfhack.curry(setFiltersUp,{items={{}}})}
--issue a job...
elseif actionid==3 then
elseif actionid==3 then --Fire
local action=df.job_type.FireBallista
if building:getSubtype()==df.siegeengine_type.Catapult then
action=df.job_type.FireCatapult
end
args={}
args.job_type=action
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}
args.from_pos=from_pos
args.pos=from_pos
--another job?
end
if args~=nil then
args.post_actions={AssignBuildingRef}
makeJob(args)
end
args.post_actions={AssignBuildingRef}
makeJob(args)
end
function putItemToBuilding(building,item)
if building:getType()==df.building_type.Table then
@ -1051,7 +1215,6 @@ function putItemToBuilding(building,item)
end
end
function usetool:openPutWindow(building)
local adv=df.global.world.units.active[0]
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,
@ -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)
end
function usetool:openSiegeWindow(building)
local args={building=building,screen=self}
dialog.showListPrompt("Engine job choice", "Choose what to do:",COLOR_WHITE,{"Turn","Load","Fire"},
dfhack.curry(siegeWeaponActionChosen,building))
dfhack.curry(siegeWeaponActionChosen,args))
end
function usetool:onWorkShopButtonClicked(building,index,choice)
local adv=df.global.world.units.active[0]
local args={unit=adv,building=building}
if df.interface_button_building_new_jobst:is_instance(choice.button) then
print("pre-click")
choice.button:click()
print("post-click",#building.jobs)
if #building.jobs>0 then
local job=building.jobs[#building.jobs-1]
args.job=job
@ -1106,7 +1268,7 @@ function usetool:openShopWindowButtoned(building,no_reset)
--]]
end
building:fillSidebarMenu()
local list={}
for id,choice in pairs(wui.choices_visible) do
table.insert(list,{text=utils.call_with_string(choice,"getLabel"),button=choice})
@ -1122,7 +1284,7 @@ function usetool:openShopWindowButtoned(building,no_reset)
end
function usetool:openShopWindow(building)
local adv=df.global.world.units.active[0]
local filter_pile=workshopJobs.getJobs(building:getType(),building:getSubtype(),building:getCustomType())
if filter_pile then
local state={unit=adv,from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z},building=building
@ -1159,17 +1321,17 @@ function usetool:armCleanTrap(building)
end
--building.trap_type==df.trap_type.PressurePlate then
--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}
if building.trap_type==df.trap_type.CageTrap then
args.job_type=df.job_type.LoadCageTrap
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
args.job_type=df.job_type.LoadStoneTrap
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
qerror("TODO")
else
@ -1181,16 +1343,16 @@ function usetool:armCleanTrap(building)
end
function usetool:hiveActions(building)
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}
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)
--InstallColonyInHive,
--CollectHiveProducts,
end
function usetool:operatePump(building)
local adv=df.global.world.units.active[0]
makeJob{unit=adv,post_actions={AssignBuildingRef},pos=adv.pos,from_pos=adv.pos,job_type=df.job_type.OperatePump,screen=self}
end
@ -1205,7 +1367,7 @@ function usetool:farmPlot(building)
end
end
--check if there tile is without plantseeds,add job
local args={unit=adv,pos=adv.pos,from_pos=adv.pos,screen=self}
if do_harvest then
args.job_type=df.job_type.HarvestPlants
@ -1298,6 +1460,11 @@ function usetool:shopMode(enable,mode,building)
self.subviews.shopLabel.visible=enable
if mode then
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
end
self.mode=mode
@ -1333,6 +1500,13 @@ function usetool:setupFields()
ui.site_id=site.id
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)
local adv=df.global.world.units.active[0]
local cur_mode=actions[(mode or 0)+1]
@ -1340,24 +1514,40 @@ function usetool:fieldInput(keys)
for code,_ in pairs(keys) do
--print(code)
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}
if code=="SELECT" 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}
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
state.pos={x=df.global.cursor.x,y=df.global.cursor.y,z=df.global.cursor.z}
else
break
end
end
for _,p in pairs(cur_mode[3] or {}) do
local ok,msg=p(state)
if ok==false then
dfhack.gui.showAnnouncement(msg,5,1)
failed=true
--First check site
local ok,msg=self:siteCheck() --TODO: some jobs might be possible without a site?
if not ok then
dfhack.gui.showAnnouncement(msg,5,1)
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
if not failed then
local ok,msg
if type(cur_mode[2])=="function" then
@ -1365,9 +1555,9 @@ function usetool:fieldInput(keys)
else
makeJob(state)
--(adv,moddedpos(adv.pos,MOVEMENT_KEYS[code]),cur_mode[2],adv.pos,cur_mode[4])
end
if code=="SELECT" then
self:sendInputToParent("LEAVESCREEN")
end
@ -1381,25 +1571,27 @@ function usetool:fieldInput(keys)
end
end
end
end
function usetool:onInput(keys)
local adv=df.global.world.units.active[0]
self:update_site()
local adv=df.global.world.units.active[0]
if keys.LEAVESCREEN then
if df.global.cursor.x~=-30000 then
self:sendInputToParent("LEAVESCREEN")
if df.global.cursor.x~=-30000 then --if not poiting at anything
self:sendInputToParent("LEAVESCREEN") --leave poiting
else
self:dismiss()
self:dismiss() --leave the adv-tools all together
CancelJob(adv)
end
elseif keys[keybinds.nextJob.key] then
elseif keys[keybinds.nextJob.key] then --next job with looping
mode=(mode+1)%#actions
elseif keys[keybinds.prevJob.key] then
elseif keys[keybinds.prevJob.key] then --prev job with looping
mode=mode-1
if mode<0 then mode=#actions-1 end
--elseif keys.A_LOOK then
-- self:sendInputToParent("A_LOOK")
elseif keys["A_SHORT_WAIT"] then
--ContinueJob(adv)
self:sendInputToParent("A_SHORT_WAIT")
@ -1417,14 +1609,7 @@ function usetool:onInput(keys)
self:fieldInput(keys)
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
function usetool:onIdle()
@ -1432,7 +1617,19 @@ function usetool:onIdle()
local job_ptr=adv.job.current_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 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
self.long_wait=false
end

@ -12,8 +12,10 @@ jobitemEditor.ATTRS{
allow_remove=false,
allow_any_item=false,
job=DEFAULT_NIL,
job_items=DEFAULT_NIL,
items=DEFAULT_NIL,
on_okay=DEFAULT_NIL,
autofill=true,
}
function update_slot_text(slot)
local items=""
@ -29,7 +31,7 @@ end
--items-> table => key-> id of job.job_items, value-> table => key (num), value => item(ref)
function jobitemEditor:init(args)
--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
self:addviews{
@ -74,6 +76,9 @@ function jobitemEditor:init(args)
}
self.assigned={}
self:fill()
if self.autofill then
self:fill_slots()
end
end
function jobitemEditor:get_slot()
local idx,choice=self.subviews.itemList:getSelected()
@ -104,6 +109,23 @@ function jobitemEditor:add_item()
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)
table.insert(slot.items,item)
slot.filled_amount=slot.filled_amount+item:getTotalDimension()
@ -124,7 +146,15 @@ end
function jobitemEditor:fill()
self.slots={}
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])
end
self.subviews.itemList:setChoices(self.slots)
@ -139,13 +169,15 @@ function jobitemEditor:jobValid()
return true
end
function jobitemEditor:commit()
for _,slot in pairs(self.slots) do
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})
if self.job then
for _,slot in pairs(self.slots) do
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
self:dismiss()
if self.on_okay then self:on_okay() end
if self.on_okay then self.on_okay(self.slots) end
end
function showItemEditor(job,item_selections)