Merge branch 'master' of https://github.com/peterix/dfhack
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 5.1 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 7.5 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 6.6 KiB |
After Width: | Height: | Size: 7.6 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 5.6 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 6.3 KiB |
@ -0,0 +1,315 @@
|
||||
/*
|
||||
https://github.com/peterix/dfhack
|
||||
Copyright (c) 2011 Petr Mrázek <peterix@gmail.com>
|
||||
|
||||
A thread-safe logging console with a line editor for windows.
|
||||
|
||||
Based on linenoise win32 port,
|
||||
copyright 2010, Jon Griffiths <jon_p_griffiths at yahoo dot com>.
|
||||
All rights reserved.
|
||||
Based on linenoise, copyright 2010, Salvatore Sanfilippo <antirez at gmail dot com>.
|
||||
The original linenoise can be found at: http://github.com/antirez/linenoise
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Redis nor the names of its contributors may be used
|
||||
to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <istream>
|
||||
#include <string>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <md5wrapper.h>
|
||||
|
||||
using std::cout;
|
||||
using std::cerr;
|
||||
using std::endl;
|
||||
|
||||
typedef unsigned char patch_byte;
|
||||
|
||||
struct BinaryPatch {
|
||||
struct Byte {
|
||||
unsigned offset;
|
||||
patch_byte old_val, new_val;
|
||||
};
|
||||
enum State {
|
||||
Conflict = 0,
|
||||
Unapplied = 1,
|
||||
Applied = 2,
|
||||
Partial = 3
|
||||
};
|
||||
|
||||
std::vector<Byte> entries;
|
||||
|
||||
bool loadDIF(std::string name);
|
||||
State checkState(const patch_byte *ptr, size_t len);
|
||||
|
||||
void apply(patch_byte *ptr, size_t len, bool newv);
|
||||
};
|
||||
|
||||
inline bool is_hex(char c)
|
||||
{
|
||||
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
|
||||
}
|
||||
|
||||
bool BinaryPatch::loadDIF(std::string name)
|
||||
{
|
||||
entries.clear();
|
||||
|
||||
std::ifstream infile(name);
|
||||
if(infile.bad())
|
||||
{
|
||||
cerr << "Cannot open file: " << name << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string s;
|
||||
while(std::getline(infile, s))
|
||||
{
|
||||
// Parse lines that begin with "[0-9a-f]+:"
|
||||
size_t idx = s.find(':');
|
||||
if (idx == std::string::npos || idx == 0 || idx > 8)
|
||||
continue;
|
||||
|
||||
bool ok = true;
|
||||
for (size_t i = 0; i < idx; i++)
|
||||
if (!is_hex(s[i]))
|
||||
ok = false;
|
||||
if (!ok)
|
||||
continue;
|
||||
|
||||
unsigned off, oval, nval;
|
||||
int nchar = 0;
|
||||
int cnt = sscanf(s.c_str(), "%x: %x %x%n", &off, &oval, &nval, &nchar);
|
||||
|
||||
if (cnt < 3)
|
||||
{
|
||||
cerr << "Could not parse: " << s << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = nchar; i < s.size(); i++)
|
||||
{
|
||||
if (!isspace(s[i]))
|
||||
{
|
||||
cerr << "Garbage at end of line: " << s << endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (oval >= 256 || nval >= 256)
|
||||
{
|
||||
cerr << "Invalid byte values: " << s << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
Byte bv = { off, patch_byte(oval), patch_byte(nval) };
|
||||
entries.push_back(bv);
|
||||
}
|
||||
|
||||
if (entries.empty())
|
||||
{
|
||||
cerr << "No lines recognized." << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BinaryPatch::State BinaryPatch::checkState(const patch_byte *ptr, size_t len)
|
||||
{
|
||||
int state = 0;
|
||||
|
||||
for (size_t i = 0; i < entries.size(); i++)
|
||||
{
|
||||
Byte &bv = entries[i];
|
||||
|
||||
if (bv.offset >= len)
|
||||
{
|
||||
cerr << "Offset out of range: 0x" << std::hex << bv.offset << std::dec << endl;
|
||||
return Conflict;
|
||||
}
|
||||
|
||||
patch_byte cv = ptr[bv.offset];
|
||||
if (bv.old_val == cv)
|
||||
state |= Unapplied;
|
||||
else if (bv.new_val == cv)
|
||||
state |= Applied;
|
||||
else
|
||||
{
|
||||
cerr << std::hex << bv.offset << ": "
|
||||
<< unsigned(bv.old_val) << " " << unsigned(bv.new_val)
|
||||
<< ", but currently " << unsigned(cv) << std::dec << endl;
|
||||
return Conflict;
|
||||
}
|
||||
}
|
||||
|
||||
return State(state);
|
||||
}
|
||||
|
||||
void BinaryPatch::apply(patch_byte *ptr, size_t len, bool newv)
|
||||
{
|
||||
for (size_t i = 0; i < entries.size(); i++)
|
||||
{
|
||||
Byte &bv = entries[i];
|
||||
assert (bv.offset < len);
|
||||
|
||||
ptr[bv.offset] = (newv ? bv.new_val : bv.old_val);
|
||||
}
|
||||
}
|
||||
|
||||
bool load_file(std::vector<patch_byte> *pvec, std::string fname)
|
||||
{
|
||||
FILE *f = fopen(fname.c_str(), "rb");
|
||||
if (!f)
|
||||
{
|
||||
cerr << "Cannot open file: " << fname << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
fseek(f, 0, SEEK_END);
|
||||
pvec->resize(ftell(f));
|
||||
fseek(f, 0, SEEK_SET);
|
||||
size_t cnt = fread(pvec->data(), 1, pvec->size(), f);
|
||||
fclose(f);
|
||||
|
||||
return cnt == pvec->size();
|
||||
}
|
||||
|
||||
bool save_file(const std::vector<patch_byte> &pvec, std::string fname)
|
||||
{
|
||||
FILE *f = fopen(fname.c_str(), "wb");
|
||||
if (!f)
|
||||
{
|
||||
cerr << "Cannot open file: " << fname << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t cnt = fwrite(pvec.data(), 1, pvec.size(), f);
|
||||
fclose(f);
|
||||
|
||||
return cnt == pvec.size();
|
||||
}
|
||||
|
||||
std::string compute_hash(const std::vector<patch_byte> &pvec)
|
||||
{
|
||||
md5wrapper md5;
|
||||
return md5.getHashFromBytes(pvec.data(), pvec.size());
|
||||
}
|
||||
|
||||
int main (int argc, char *argv[])
|
||||
{
|
||||
if (argc <= 3)
|
||||
{
|
||||
cerr << "Usage: binpatch check|apply|remove <exe> <patch>" << endl;
|
||||
return 2;
|
||||
}
|
||||
|
||||
std::string cmd = argv[1];
|
||||
|
||||
if (cmd != "check" && cmd != "apply" && cmd != "remove")
|
||||
{
|
||||
cerr << "Invalid command: " << cmd << endl;
|
||||
return 2;
|
||||
}
|
||||
|
||||
std::string exe_file = argv[2];
|
||||
std::vector<patch_byte> bindata;
|
||||
if (!load_file(&bindata, exe_file))
|
||||
return 2;
|
||||
|
||||
BinaryPatch patch;
|
||||
if (!patch.loadDIF(argv[3]))
|
||||
return 2;
|
||||
|
||||
BinaryPatch::State state = patch.checkState(bindata.data(), bindata.size());
|
||||
if (state == BinaryPatch::Conflict)
|
||||
return 1;
|
||||
|
||||
if (cmd == "check")
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case BinaryPatch::Unapplied:
|
||||
cout << "Currently not applied." << endl;
|
||||
break;
|
||||
case BinaryPatch::Applied:
|
||||
cout << "Currently applied." << endl;
|
||||
break;
|
||||
case BinaryPatch::Partial:
|
||||
cout << "Currently partially applied." << endl;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
else if (cmd == "apply")
|
||||
{
|
||||
if (state == BinaryPatch::Applied)
|
||||
{
|
||||
cout << "Already applied." << endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
patch.apply(bindata.data(), bindata.size(), true);
|
||||
}
|
||||
else if (cmd == "remove")
|
||||
{
|
||||
if (state == BinaryPatch::Unapplied)
|
||||
{
|
||||
cout << "Already removed." << endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
patch.apply(bindata.data(), bindata.size(), false);
|
||||
}
|
||||
|
||||
if (!save_file(bindata, exe_file + ".bak"))
|
||||
{
|
||||
cerr << "Could not create backup." << endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!save_file(bindata, exe_file))
|
||||
return 1;
|
||||
|
||||
cout << "Patched " << patch.entries.size()
|
||||
<< " bytes, new hash: " << compute_hash(bindata) << endl;
|
||||
return 0;
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
#ifndef EVENT_MANAGER_H_INCLUDED
|
||||
#define EVENT_MANAGER_H_INCLUDED
|
||||
|
||||
#include "Core.h"
|
||||
#include "Export.h"
|
||||
#include "ColorText.h"
|
||||
#include "PluginManager.h"
|
||||
#include "Console.h"
|
||||
|
||||
namespace DFHack {
|
||||
namespace EventManager {
|
||||
namespace EventType {
|
||||
enum EventType {
|
||||
TICK,
|
||||
JOB_INITIATED,
|
||||
JOB_COMPLETED,
|
||||
UNIT_DEATH,
|
||||
ITEM_CREATED,
|
||||
BUILDING,
|
||||
CONSTRUCTION,
|
||||
SYNDROME,
|
||||
INVASION,
|
||||
EVENT_MAX
|
||||
};
|
||||
}
|
||||
|
||||
struct EventHandler {
|
||||
void (*eventHandler)(color_ostream&, void*); //called when the event happens
|
||||
int32_t freq;
|
||||
|
||||
EventHandler(void (*eventHandlerIn)(color_ostream&, void*), int32_t freqIn): eventHandler(eventHandlerIn), freq(freqIn) {
|
||||
}
|
||||
|
||||
bool operator==(EventHandler& handle) const {
|
||||
return eventHandler == handle.eventHandler && freq == handle.freq;
|
||||
}
|
||||
bool operator!=(EventHandler& handle) const {
|
||||
return !( *this == handle);
|
||||
}
|
||||
};
|
||||
|
||||
struct SyndromeData {
|
||||
int32_t unitId;
|
||||
int32_t syndromeIndex;
|
||||
SyndromeData(int32_t unitId_in, int32_t syndromeIndex_in): unitId(unitId_in), syndromeIndex(syndromeIndex_in) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
DFHACK_EXPORT void registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin);
|
||||
DFHACK_EXPORT void registerTick(EventHandler handler, int32_t when, Plugin* plugin, bool absolute=false);
|
||||
DFHACK_EXPORT void unregister(EventType::EventType e, EventHandler handler, Plugin* plugin);
|
||||
DFHACK_EXPORT void unregisterAll(Plugin* plugin);
|
||||
void manageEvents(color_ostream& out);
|
||||
void onStateChange(color_ostream& out, state_change_event event);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@ -1,70 +0,0 @@
|
||||
/*
|
||||
https://github.com/peterix/dfhack
|
||||
Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any
|
||||
damages arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any
|
||||
purpose, including commercial applications, and to alter it and
|
||||
redistribute it freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must
|
||||
not claim that you wrote the original software. If you use this
|
||||
software in a product, an acknowledgment in the product documentation
|
||||
would be appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and
|
||||
must not be misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef CL_MOD_VEGETATION
|
||||
#define CL_MOD_VEGETATION
|
||||
/**
|
||||
* \defgroup grp_vegetation Vegetation : stuff that grows and gets cut down or trampled by dwarves
|
||||
* @ingroup grp_modules
|
||||
*/
|
||||
|
||||
#include "Export.h"
|
||||
#include "DataDefs.h"
|
||||
#include "df/plant.h"
|
||||
|
||||
namespace DFHack
|
||||
{
|
||||
namespace Vegetation
|
||||
{
|
||||
const uint32_t sapling_to_tree_threshold = 120 * 28 * 12 * 3; // 3 years
|
||||
|
||||
// "Simplified" copy of plant
|
||||
struct t_plant {
|
||||
df::language_name name;
|
||||
df::plant_flags flags;
|
||||
int16_t material;
|
||||
df::coord pos;
|
||||
int32_t grow_counter;
|
||||
uint16_t temperature_1;
|
||||
uint16_t temperature_2;
|
||||
int32_t is_burning;
|
||||
int32_t hitpoints;
|
||||
int16_t update_order;
|
||||
//std::vector<void *> unk1;
|
||||
//int32_t unk2;
|
||||
//uint16_t temperature_3;
|
||||
//uint16_t temperature_4;
|
||||
//uint16_t temperature_5;
|
||||
// Pointer to original object, in case you want to modify it
|
||||
df::plant *origin;
|
||||
};
|
||||
|
||||
DFHACK_EXPORT bool isValid();
|
||||
DFHACK_EXPORT uint32_t getCount();
|
||||
DFHACK_EXPORT df::plant * getPlant(const int32_t index);
|
||||
DFHACK_EXPORT bool copyPlant (const int32_t index, t_plant &out);
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,123 @@
|
||||
-- Simple binary patch with IDA dif file support.
|
||||
|
||||
local _ENV = mkmodule('binpatch')
|
||||
|
||||
local function load_patch(name)
|
||||
local filename = name
|
||||
if not string.match(filename, '[./\\]') then
|
||||
filename = dfhack.getHackPath()..'/patches/'..dfhack.getDFVersion()..'/'..name..'.dif'
|
||||
end
|
||||
|
||||
local file, err = io.open(filename, 'r')
|
||||
if not file then
|
||||
if string.match(err, ': No such file or directory') then
|
||||
return nil, 'patch not found'
|
||||
end
|
||||
end
|
||||
|
||||
local old_bytes = {}
|
||||
local new_bytes = {}
|
||||
|
||||
for line in file:lines() do
|
||||
if string.match(line, '^%x+:') then
|
||||
local offset, oldv, newv = string.match(line, '^(%x+):%s*(%x+)%s+(%x+)%s*$')
|
||||
if not offset then
|
||||
file:close()
|
||||
return nil, 'could not parse: '..line
|
||||
end
|
||||
|
||||
offset, oldv, newv = tonumber(offset,16), tonumber(oldv,16), tonumber(newv,16)
|
||||
if oldv > 255 or newv > 255 then
|
||||
file:close()
|
||||
return nil, 'invalid byte values: '..line
|
||||
end
|
||||
|
||||
old_bytes[offset] = oldv
|
||||
new_bytes[offset] = newv
|
||||
end
|
||||
end
|
||||
|
||||
return { name = name, old_bytes = old_bytes, new_bytes = new_bytes }
|
||||
end
|
||||
|
||||
local function rebase_table(input)
|
||||
local output = {}
|
||||
local base = dfhack.internal.getImageBase()
|
||||
for k,v in pairs(input) do
|
||||
local offset = dfhack.internal.adjustOffset(k)
|
||||
if not offset then
|
||||
return nil, string.format('invalid offset: %x', k)
|
||||
end
|
||||
output[base + offset] = v
|
||||
end
|
||||
return output
|
||||
end
|
||||
|
||||
local function rebase_patch(patch)
|
||||
local nold, err = rebase_table(patch.old_bytes)
|
||||
if not nold then return nil, err end
|
||||
local nnew, err = rebase_table(patch.new_bytes)
|
||||
if not nnew then return nil, err end
|
||||
return { name = patch.name, old_bytes = nold, new_bytes = nnew }
|
||||
end
|
||||
|
||||
BinaryPatch = defclass(BinaryPatch)
|
||||
|
||||
BinaryPatch.ATTRS {
|
||||
name = DEFAULT_NIL,
|
||||
old_bytes = DEFAULT_NIL,
|
||||
new_bytes = DEFAULT_NIL,
|
||||
}
|
||||
|
||||
function load_dif_file(name)
|
||||
local patch, err = load_patch(name)
|
||||
if not patch then return nil, err end
|
||||
|
||||
local rpatch, err = rebase_patch(patch)
|
||||
if not rpatch then return nil, err end
|
||||
|
||||
return BinaryPatch(rpatch)
|
||||
end
|
||||
|
||||
function BinaryPatch:status()
|
||||
local old_ok, err, addr = dfhack.internal.patchBytes({}, self.old_bytes)
|
||||
if old_ok then
|
||||
return 'removed'
|
||||
elseif dfhack.internal.patchBytes({}, self.new_bytes) then
|
||||
return 'applied'
|
||||
else
|
||||
return 'conflict', addr
|
||||
end
|
||||
end
|
||||
|
||||
function BinaryPatch:isApplied()
|
||||
return dfhack.internal.patchBytes({}, self.new_bytes)
|
||||
end
|
||||
|
||||
function BinaryPatch:apply()
|
||||
local ok, err, addr = dfhack.internal.patchBytes(self.new_bytes, self.old_bytes)
|
||||
if ok then
|
||||
return true, 'applied the patch'
|
||||
elseif dfhack.internal.patchBytes({}, self.new_bytes) then
|
||||
return true, 'patch is already applied'
|
||||
else
|
||||
return false, string.format('conflict at address %x', addr)
|
||||
end
|
||||
end
|
||||
|
||||
function BinaryPatch:isRemoved()
|
||||
return dfhack.internal.patchBytes({}, self.old_bytes)
|
||||
end
|
||||
|
||||
function BinaryPatch:remove()
|
||||
local ok, err, addr = dfhack.internal.patchBytes(self.old_bytes, self.new_bytes)
|
||||
if ok then
|
||||
return true, 'removed the patch'
|
||||
elseif dfhack.internal.patchBytes({}, self.old_bytes) then
|
||||
return true, 'patch is already removed'
|
||||
else
|
||||
return false, string.format('conflict at address %x', addr)
|
||||
end
|
||||
end
|
||||
|
||||
return _ENV
|
@ -0,0 +1,591 @@
|
||||
local _ENV = mkmodule('dfhack.workshops')
|
||||
|
||||
local utils = require 'utils'
|
||||
|
||||
input_filter_defaults = {
|
||||
item_type = -1,
|
||||
item_subtype = -1,
|
||||
mat_type = -1,
|
||||
mat_index = -1,
|
||||
flags1 = {},
|
||||
-- Instead of noting those that allow artifacts, mark those that forbid them.
|
||||
-- Leaves actually enabling artifacts to the discretion of the API user,
|
||||
-- which is the right thing because unlike the game UI these filters are
|
||||
-- used in a way that does not give the user a chance to choose manually.
|
||||
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 fuel={item_type=df.item_type.BAR,mat_type=df.builtin_mats.COAL}
|
||||
jobs_furnace={
|
||||
[df.furnace_type.Smelter]={
|
||||
{
|
||||
name="Melt metal object",
|
||||
items={fuel,{flags2={allow_melt_dump=true}}},--also maybe melt_designated
|
||||
job_fields={job_type=df.job_type.MeltMetalObject}
|
||||
}
|
||||
},
|
||||
[df.furnace_type.MagmaSmelter]={
|
||||
{
|
||||
name="Melt metal object",
|
||||
items={{flags2={allow_melt_dump=true}}},--also maybe melt_designated
|
||||
job_fields={job_type=df.job_type.MeltMetalObject}
|
||||
}
|
||||
},
|
||||
--[[ [df.furnace_type.MetalsmithsForge]={
|
||||
unpack(concat(furnaces,mechanism,anvil,crafts,coins,flask))
|
||||
|
||||
},
|
||||
]]
|
||||
--MetalsmithsForge,
|
||||
--MagmaForge
|
||||
--[[
|
||||
forges:
|
||||
weapons and ammo-> from raws...
|
||||
armor -> raws
|
||||
furniture -> builtins?
|
||||
siege eq-> builtin (only balista head)
|
||||
trap eq -> from raws+ mechanisms
|
||||
other object-> anvil, crafts, goblets,toys,instruments,nestbox... (raws?) flask, coins,stud with iron
|
||||
metal clothing-> raws???
|
||||
]]
|
||||
[df.furnace_type.GlassFurnace]={
|
||||
{
|
||||
name="collect sand",
|
||||
items={},
|
||||
job_fields={job_type=df.job_type.CollectSand}
|
||||
},
|
||||
--glass crafts x3
|
||||
},
|
||||
[df.furnace_type.WoodFurnace]={
|
||||
defaults={item_type=df.item_type.WOOD,vector_id=df.job_item_vector_id.WOOD},
|
||||
{
|
||||
name="make charcoal",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.MakeCharcoal}
|
||||
},
|
||||
{
|
||||
name="make ash",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.MakeAsh}
|
||||
}
|
||||
},
|
||||
[df.furnace_type.Kiln]={
|
||||
{
|
||||
name="collect clay",
|
||||
items={},
|
||||
job_fields={job_type=df.job_type.CollectClay}
|
||||
}
|
||||
},
|
||||
}
|
||||
jobs_workshop={
|
||||
|
||||
[df.workshop_type.Jewelers]={
|
||||
{
|
||||
name="cut gems",
|
||||
items={{item_type=df.item_type.ROUGH,flags1={unrotten=true}}},
|
||||
job_fields={job_type=df.job_type.CutGems}
|
||||
},
|
||||
{
|
||||
name="encrust finished goods with gems",
|
||||
items={{item_type=df.item_type.SMALLGEM},{flags1={improvable=true,finished_goods=true}}},
|
||||
job_fields={job_type=df.job_type.EncrustWithGems}
|
||||
},
|
||||
{
|
||||
name="encrust ammo with gems",
|
||||
items={{item_type=df.item_type.SMALLGEM},{flags1={improvable=true,ammo=true}}},
|
||||
job_fields={job_type=df.job_type.EncrustWithGems}
|
||||
},
|
||||
{
|
||||
name="encrust furniture with gems",
|
||||
items={{item_type=df.item_type.SMALLGEM},{flags1={improvable=true,furniture=true}}},
|
||||
job_fields={job_type=df.job_type.EncrustWithGems}
|
||||
},
|
||||
},
|
||||
[df.workshop_type.Fishery]={
|
||||
{
|
||||
name="prepare raw fish",
|
||||
items={{item_type=df.item_type.FISH_RAW,flags1={unrotten=true}}},
|
||||
job_fields={job_type=df.job_type.PrepareRawFish}
|
||||
},
|
||||
{
|
||||
name="extract from raw fish",
|
||||
items={{flags1={unrotten=true,extract_bearing_fish=true}},{item_type=df.item_type.FLASK,flags1={empty=true,glass=true}}},
|
||||
job_fields={job_type=df.job_type.ExtractFromRawFish}
|
||||
},
|
||||
{
|
||||
name="catch live fish",
|
||||
items={},
|
||||
job_fields={job_type=df.job_type.CatchLiveFish}
|
||||
}, -- no items?
|
||||
},
|
||||
[df.workshop_type.Still]={
|
||||
{
|
||||
name="brew drink",
|
||||
items={{flags1={distillable=true},vector_id=22},{flags1={empty=true},flags3={food_storage=true}}},
|
||||
job_fields={job_type=df.job_type.BrewDrink}
|
||||
},
|
||||
{
|
||||
name="extract from plants",
|
||||
items={{item_type=df.item_type.PLANT,flags1={unrotten=true,extract_bearing_plant=true}},{item_type=df.item_type.FLASK,flags1={empty=true}}},
|
||||
job_fields={job_type=df.job_type.ExtractFromPlants}
|
||||
},
|
||||
--mead from raws?
|
||||
},
|
||||
[df.workshop_type.Masons]={
|
||||
defaults={item_type=df.item_type.BOULDER,item_subtype=-1,vector_id=df.job_item_vector_id.BOULDER, mat_type=0,mat_index=-1,flags3={hard=true}},--flags2={non_economic=true},
|
||||
{
|
||||
name="construct armor stand",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructArmorStand}
|
||||
},
|
||||
|
||||
{
|
||||
name="construct blocks",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructBlocks}
|
||||
},
|
||||
{
|
||||
name="construct throne",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructThrone}
|
||||
},
|
||||
{
|
||||
name="construct coffin",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructCoffin}
|
||||
},
|
||||
{
|
||||
name="construct door",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructDoor}
|
||||
},
|
||||
{
|
||||
name="construct floodgate",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructFloodgate}
|
||||
},
|
||||
{
|
||||
name="construct hatch cover",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructHatchCover}
|
||||
},
|
||||
{
|
||||
name="construct grate",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructGrate}
|
||||
},
|
||||
{
|
||||
name="construct cabinet",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructCabinet}
|
||||
},
|
||||
{
|
||||
name="construct chest",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructChest}
|
||||
},
|
||||
{
|
||||
name="construct statue",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructStatue}
|
||||
},
|
||||
{
|
||||
name="construct slab",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructSlab}
|
||||
},
|
||||
{
|
||||
name="construct table",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructTable}
|
||||
},
|
||||
{
|
||||
name="construct weapon rack",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructWeaponRack}
|
||||
},
|
||||
{
|
||||
name="construct quern",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructQuern}
|
||||
},
|
||||
{
|
||||
name="construct millstone",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructMillstone}
|
||||
},
|
||||
},
|
||||
[df.workshop_type.Carpenters]={
|
||||
--training weapons, wooden shields
|
||||
defaults={item_type=df.item_type.WOOD,vector_id=df.job_item_vector_id.WOOD},
|
||||
|
||||
{
|
||||
name="make barrel",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.MakeBarrel}
|
||||
},
|
||||
|
||||
{
|
||||
name="make bucket",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.MakeBucket}
|
||||
},
|
||||
{
|
||||
name="make animal trap",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.MakeAnimalTrap}
|
||||
},
|
||||
{
|
||||
name="make cage",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.MakeCage}
|
||||
},
|
||||
{
|
||||
name="construct bed",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructBed}
|
||||
},
|
||||
{
|
||||
name="construct bin",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructBin}
|
||||
},
|
||||
{
|
||||
name="construct armor stand",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructArmorStand}
|
||||
},
|
||||
{
|
||||
name="construct blocks",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructBlocks}
|
||||
},
|
||||
{
|
||||
name="construct throne",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructThrone}
|
||||
},
|
||||
{
|
||||
name="construct coffin",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructCoffin}
|
||||
},
|
||||
{
|
||||
name="construct door",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructDoor}
|
||||
},
|
||||
{
|
||||
name="construct floodgate",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructFloodgate}
|
||||
},
|
||||
{
|
||||
name="construct hatch cover",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructHatchCover}
|
||||
},
|
||||
{
|
||||
name="construct grate",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructGrate}
|
||||
},
|
||||
{
|
||||
name="construct cabinet",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructCabinet}
|
||||
},
|
||||
{
|
||||
name="construct chest",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructChest}
|
||||
},
|
||||
{
|
||||
name="construct statue",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructStatue}
|
||||
},
|
||||
{
|
||||
name="construct table",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructTable}
|
||||
},
|
||||
{
|
||||
name="construct weapon rack",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructWeaponRack}
|
||||
},
|
||||
{
|
||||
name="construct splint",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructSplint}
|
||||
},
|
||||
{
|
||||
name="construct crutch",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructCrutch}
|
||||
},
|
||||
},
|
||||
[df.workshop_type.Kitchen]={
|
||||
--mat_type=2,3,4
|
||||
defaults={flags1={unrotten=true,cookable=true}},
|
||||
{
|
||||
name="prepare easy meal",
|
||||
items={{flags1={solid=true}},{}},
|
||||
job_fields={job_type=df.job_type.PrepareMeal,mat_type=2}
|
||||
},
|
||||
{
|
||||
name="prepare fine meal",
|
||||
items={{flags1={solid=true}},{},{}},
|
||||
job_fields={job_type=df.job_type.PrepareMeal,mat_type=3}
|
||||
},
|
||||
{
|
||||
name="prepare lavish meal",
|
||||
items={{flags1={solid=true}},{},{},{}},
|
||||
job_fields={job_type=df.job_type.PrepareMeal,mat_type=4}
|
||||
},
|
||||
},
|
||||
[df.workshop_type.Butchers]={
|
||||
{
|
||||
name="butcher an animal",
|
||||
items={{flags1={butcherable=true,unrotten=true,nearby=true}}},
|
||||
job_fields={job_type=df.job_type.ButcherAnimal}
|
||||
},
|
||||
{
|
||||
name="extract from land animal",
|
||||
items={{flags1={extract_bearing_vermin=true,unrotten=true}},{item_type=df.item_type.FLASK,flags1={empty=true,glass=true}}},
|
||||
job_fields={job_type=df.job_type.ExtractFromLandAnimal}
|
||||
},
|
||||
{
|
||||
name="catch live land animal",
|
||||
items={},
|
||||
job_fields={job_type=df.job_type.CatchLiveLandAnimal}
|
||||
},
|
||||
},
|
||||
[df.workshop_type.Mechanics]={
|
||||
{
|
||||
name="construct mechanisms",
|
||||
items={{item_type=df.item_type.BOULDER,item_subtype=-1,vector_id=df.job_item_vector_id.BOULDER, mat_type=0,mat_index=-1,quantity=1,
|
||||
flags3={hard=true}}},
|
||||
job_fields={job_type=df.job_type.ConstructMechanisms}
|
||||
},
|
||||
{
|
||||
name="construct traction bench",
|
||||
items={{item_type=df.item_type.TABLE},{item_type=df.item_type.MECHANISM},{item_type=df.item_type.CHAIN}},
|
||||
job_fields={job_type=df.job_type.ConstructTractionBench}
|
||||
},
|
||||
},
|
||||
[df.workshop_type.Loom]={
|
||||
{
|
||||
name="weave plant thread cloth",
|
||||
items={{item_type=df.item_type.THREAD,quantity=15000,min_dimension=15000,flags1={collected=true},flags2={plant=true}}},
|
||||
job_fields={job_type=df.job_type.WeaveCloth}
|
||||
},
|
||||
{
|
||||
name="weave silk thread cloth",
|
||||
items={{item_type=df.item_type.THREAD,quantity=15000,min_dimension=15000,flags1={collected=true},flags2={silk=true}}},
|
||||
job_fields={job_type=df.job_type.WeaveCloth}
|
||||
},
|
||||
{
|
||||
name="weave yarn cloth",
|
||||
items={{item_type=df.item_type.THREAD,quantity=15000,min_dimension=15000,flags1={collected=true},flags2={yarn=true}}},
|
||||
job_fields={job_type=df.job_type.WeaveCloth}
|
||||
},
|
||||
{
|
||||
name="weave inorganic cloth",
|
||||
items={{item_type=df.item_type.THREAD,quantity=15000,min_dimension=15000,flags1={collected=true},mat_type=0}},
|
||||
job_fields={job_type=df.job_type.WeaveCloth}
|
||||
},
|
||||
{
|
||||
name="collect webs",
|
||||
items={{item_type=df.item_type.THREAD,quantity=10,min_dimension=10,flags1={undisturbed=true}}},
|
||||
job_fields={job_type=df.job_type.CollectWebs}
|
||||
},
|
||||
},
|
||||
[df.workshop_type.Leatherworks]={
|
||||
defaults={item_type=SKIN_TANNED},
|
||||
{
|
||||
name="construct leather bag",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.ConstructChest}
|
||||
},
|
||||
{
|
||||
name="construct waterskin",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.MakeFlask}
|
||||
},
|
||||
{
|
||||
name="construct backpack",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.MakeBackpack}
|
||||
},
|
||||
{
|
||||
name="construct quiver",
|
||||
items={{}},
|
||||
job_fields={job_type=df.job_type.MakeQuiver}
|
||||
},
|
||||
{
|
||||
name="sew leather image",
|
||||
items={{item_type=-1,flags1={empty=true},flags2={sewn_imageless=true}},{}},
|
||||
job_fields={job_type=df.job_type.SewImage}
|
||||
},
|
||||
},
|
||||
[df.workshop_type.Dyers]={
|
||||
{
|
||||
name="dye thread",
|
||||
items={{item_type=df.item_type.THREAD,quantity=15000,min_dimension=15000,flags1={collected=true},flags2={dyeable=true}},
|
||||
{flags1={unrotten=true},flags2={dye=true}}},
|
||||
job_fields={job_type=df.job_type.DyeThread}
|
||||
},
|
||||
{
|
||||
name="dye cloth",
|
||||
items={{item_type=df.item_type.CLOTH,quantity=10000,min_dimension=10000,flags2={dyeable=true}},
|
||||
{flags1={unrotten=true},flags2={dye=true}}},
|
||||
job_fields={job_type=df.job_type.DyeThread}
|
||||
},
|
||||
},
|
||||
[df.workshop_type.Siege]={
|
||||
{
|
||||
name="construct balista parts",
|
||||
items={{item_type=df.item_type.WOOD}},
|
||||
job_fields={job_type=df.job_type.ConstructBallistaParts}
|
||||
},
|
||||
{
|
||||
name="construct catapult parts",
|
||||
items={{item_type=df.item_type.WOOD}},
|
||||
job_fields={job_type=df.job_type.ConstructCatapultParts}
|
||||
},
|
||||
{
|
||||
name="assemble balista arrow",
|
||||
items={{item_type=df.item_type.WOOD}},
|
||||
job_fields={job_type=df.job_type.AssembleSiegeAmmo}
|
||||
},
|
||||
{
|
||||
name="assemble tipped balista arrow",
|
||||
items={{item_type=df.item_type.WOOD},{item_type=df.item_type.BALLISTAARROWHEAD}},
|
||||
job_fields={job_type=df.job_type.AssembleSiegeAmmo}
|
||||
},
|
||||
},
|
||||
}
|
||||
local function matchIds(bid1,wid1,cid1,bid2,wid2,cid2)
|
||||
if bid1~=-1 and bid2~=-1 and bid1~=bid2 then
|
||||
return false
|
||||
end
|
||||
if wid1~=-1 and wid2~=-1 and wid1~=wid2 then
|
||||
return false
|
||||
end
|
||||
if cid1~=-1 and cid2~=-1 and cid1~=cid2 then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
local function scanRawsReaction(buildingId,workshopId,customId)
|
||||
local ret={}
|
||||
for idx,reaction in ipairs(df.global.world.raws.reactions) do
|
||||
for k,v in pairs(reaction.building.type) do
|
||||
if matchIds(buildingId,workshopId,customId,v,reaction.building.subtype[k],reaction.building.custom[k]) then
|
||||
table.insert(ret,reaction)
|
||||
end
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
local function reagentToJobItem(reagent,react_id,reagentId)
|
||||
local ret_item
|
||||
ret_item=utils.clone_with_default(reagent, input_filter_defaults)
|
||||
ret_item.reaction_id=react_id
|
||||
ret_item.reagent_index=reagentId
|
||||
return ret_item
|
||||
end
|
||||
local function addReactionJobs(ret,bid,wid,cid)
|
||||
local reactions=scanRawsReaction(bid,wid or -1,cid or -1)
|
||||
for idx,react in pairs(reactions) do
|
||||
local job={name=react.name,
|
||||
items={},job_fields={job_type=df.job_type.CustomReaction,reaction_name=react.code}
|
||||
}
|
||||
for reagentId,reagent in pairs(react.reagents) do
|
||||
table.insert(job.items,reagentToJobItem(reagent,idx,reagentId))
|
||||
end
|
||||
if react.flags.FUEL then
|
||||
table.insert(job.items,fuel)
|
||||
end
|
||||
table.insert(ret,job)
|
||||
end
|
||||
end
|
||||
local function scanRawsOres()
|
||||
local ret={}
|
||||
for idx,ore in ipairs(df.global.world.raws.inorganics) do
|
||||
if #ore.metal_ore.mat_index~=0 then
|
||||
ret[idx]=ore
|
||||
end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
local function addSmeltJobs(ret,use_fuel)
|
||||
local ores=scanRawsOres()
|
||||
for idx,ore in pairs(ores) do
|
||||
print("adding:",ore.material.state_name.Solid)
|
||||
printall(ore)
|
||||
local job={name="smelt "..ore.material.state_name.Solid,job_fields={job_type=df.job_type.SmeltOre,mat_type=df.builtin_mats.INORGANIC,mat_index=idx},items={
|
||||
{item_type=df.item_type.BOULDER,mat_type=df.builtin_mats.INORGANIC,mat_index=idx,vector_id=df.job_item_vector_id.BOULDER}}}
|
||||
if use_fuel then
|
||||
table.insert(job.items,fuel)
|
||||
end
|
||||
table.insert(ret,job)
|
||||
end
|
||||
return ret
|
||||
end
|
||||
function getJobs(buildingId,workshopId,customId)
|
||||
local ret={}
|
||||
local c_jobs
|
||||
if buildingId==df.building_type.Workshop then
|
||||
c_jobs=jobs_workshop[workshopId]
|
||||
elseif buildingId==df.building_type.Furnace then
|
||||
c_jobs=jobs_furnace[workshopId]
|
||||
|
||||
if workshopId == df.furnace_type.Smelter or workshopId == df.furnace_type.MagmaSmelter then
|
||||
c_jobs=utils.clone(c_jobs,true)
|
||||
addSmeltJobs(c_jobs,workshopId == df.furnace_type.Smelter)
|
||||
end
|
||||
else
|
||||
return nil
|
||||
end
|
||||
if c_jobs==nil then
|
||||
c_jobs={}
|
||||
else
|
||||
c_jobs=utils.clone(c_jobs,true)
|
||||
end
|
||||
|
||||
addReactionJobs(c_jobs,buildingId,workshopId,customId)
|
||||
for jobId,contents in pairs(c_jobs) do
|
||||
if jobId~="defaults" then
|
||||
local entry={}
|
||||
entry.name=contents.name
|
||||
local lclDefaults=utils.clone(input_filter_defaults,true)
|
||||
if c_jobs.defaults ~=nil then
|
||||
utils.assign(lclDefaults,c_jobs.defaults)
|
||||
end
|
||||
entry.items={}
|
||||
for k,item in pairs(contents.items) do
|
||||
entry.items[k]=utils.clone(lclDefaults,true)
|
||||
utils.assign(entry.items[k],item)
|
||||
end
|
||||
if contents.job_fields~=nil then
|
||||
entry.job_fields={}
|
||||
utils.assign(entry.job_fields,contents.job_fields)
|
||||
end
|
||||
ret[jobId]=entry
|
||||
end
|
||||
end
|
||||
--get jobs, add in from raws
|
||||
return ret
|
||||
end
|
||||
return _ENV
|
@ -0,0 +1,285 @@
|
||||
-- Stock dialog for selecting buildings
|
||||
|
||||
local _ENV = mkmodule('gui.buildings')
|
||||
|
||||
local gui = require('gui')
|
||||
local widgets = require('gui.widgets')
|
||||
local dlg = require('gui.dialogs')
|
||||
local utils = require('utils')
|
||||
|
||||
ARROW = string.char(26)
|
||||
|
||||
WORKSHOP_ABSTRACT={
|
||||
[df.building_type.Civzone]=true,[df.building_type.Stockpile]=true,
|
||||
}
|
||||
WORKSHOP_SPECIAL={
|
||||
[df.building_type.Workshop]=true,[df.building_type.Furnace]=true,[df.building_type.Trap]=true,
|
||||
[df.building_type.Construction]=true,[df.building_type.SiegeEngine]=true
|
||||
}
|
||||
BuildingDialog = defclass(BuildingDialog, gui.FramedScreen)
|
||||
|
||||
BuildingDialog.focus_path = 'BuildingDialog'
|
||||
|
||||
BuildingDialog.ATTRS{
|
||||
prompt = 'Type or select a building from this list',
|
||||
frame_style = gui.GREY_LINE_FRAME,
|
||||
frame_inset = 1,
|
||||
frame_title = 'Select Building',
|
||||
-- new attrs
|
||||
none_caption = 'none',
|
||||
hide_none = false,
|
||||
use_abstract = true,
|
||||
use_workshops = true,
|
||||
use_tool_workshop=true,
|
||||
use_furnace = true,
|
||||
use_construction = true,
|
||||
use_siege = true,
|
||||
use_trap = true,
|
||||
use_custom = true,
|
||||
building_filter = DEFAULT_NIL,
|
||||
on_select = DEFAULT_NIL,
|
||||
on_cancel = DEFAULT_NIL,
|
||||
on_close = DEFAULT_NIL,
|
||||
}
|
||||
|
||||
function BuildingDialog:init(info)
|
||||
self:addviews{
|
||||
widgets.Label{
|
||||
text = {
|
||||
self.prompt, '\n\n',
|
||||
'Category: ', { text = self:cb_getfield('context_str'), pen = COLOR_CYAN }
|
||||
},
|
||||
text_pen = COLOR_WHITE,
|
||||
frame = { l = 0, t = 0 },
|
||||
},
|
||||
widgets.Label{
|
||||
view_id = 'back',
|
||||
visible = false,
|
||||
text = { { key = 'LEAVESCREEN', text = ': Back' } },
|
||||
frame = { r = 0, b = 0 },
|
||||
auto_width = true,
|
||||
},
|
||||
widgets.FilteredList{
|
||||
view_id = 'list',
|
||||
not_found_label = 'No matching buildings',
|
||||
frame = { l = 0, r = 0, t = 4, b = 2 },
|
||||
icon_width = 2,
|
||||
on_submit = self:callback('onSubmitItem'),
|
||||
},
|
||||
widgets.Label{
|
||||
text = { {
|
||||
key = 'SELECT', text = ': Select',
|
||||
disabled = function() return not self.subviews.list:canSubmit() end
|
||||
} },
|
||||
frame = { l = 0, b = 0 },
|
||||
}
|
||||
}
|
||||
self:initBuiltinMode()
|
||||
end
|
||||
|
||||
function BuildingDialog:getWantedFrameSize(rect)
|
||||
return math.max(self.frame_width or 40, #self.prompt), math.min(28, rect.height-8)
|
||||
end
|
||||
|
||||
function BuildingDialog:onDestroy()
|
||||
if self.on_close then
|
||||
self.on_close()
|
||||
end
|
||||
end
|
||||
|
||||
function BuildingDialog:initBuiltinMode()
|
||||
local choices = {}
|
||||
if not self.hide_none then
|
||||
table.insert(choices, { text = self.none_caption, type_id = -1, subtype_id = -1, custom_id=-1})
|
||||
end
|
||||
|
||||
if self.use_workshops then
|
||||
table.insert(choices, {
|
||||
icon = ARROW, text = 'workshop', key = 'CUSTOM_SHIFT_W',
|
||||
cb = self:callback('initWorkshopMode')
|
||||
})
|
||||
end
|
||||
if self.use_furnace then
|
||||
table.insert(choices, {
|
||||
icon = ARROW, text = 'furnaces', key = 'CUSTOM_SHIFT_F',
|
||||
cb = self:callback('initFurnaceMode')
|
||||
})
|
||||
end
|
||||
if self.use_trap then
|
||||
table.insert(choices, {
|
||||
icon = ARROW, text = 'traps', key = 'CUSTOM_SHIFT_T',
|
||||
cb = self:callback('initTrapMode')
|
||||
})
|
||||
end
|
||||
if self.use_construction then
|
||||
table.insert(choices, {
|
||||
icon = ARROW, text = 'constructions', key = 'CUSTOM_SHIFT_C',
|
||||
cb = self:callback('initConstructionMode')
|
||||
})
|
||||
end
|
||||
if self.use_siege then
|
||||
table.insert(choices, {
|
||||
icon = ARROW, text = 'siege engine', key = 'CUSTOM_SHIFT_S',
|
||||
cb = self:callback('initSiegeMode')
|
||||
})
|
||||
end
|
||||
if self.use_custom then
|
||||
table.insert(choices, {
|
||||
icon = ARROW, text = 'custom workshop', key = 'CUSTOM_SHIFT_U',
|
||||
cb = self:callback('initCustomMode')
|
||||
})
|
||||
end
|
||||
|
||||
|
||||
|
||||
for i=0,df.building_type._last_item do
|
||||
if (not WORKSHOP_ABSTRACT[i] or self.use_abstract)and not WORKSHOP_SPECIAL[i] then
|
||||
self:addBuilding(choices, df.building_type[i], i, -1,-1,nil)
|
||||
end
|
||||
end
|
||||
|
||||
self:pushContext('Any building', choices)
|
||||
end
|
||||
|
||||
function BuildingDialog:initWorkshopMode()
|
||||
local choices = {}
|
||||
|
||||
for i=0,df.workshop_type._last_item do
|
||||
if i~=df.workshop_type.Custom and (i~=df.workshop_type.Tool or self.use_tool_workshop) then
|
||||
self:addBuilding(choices, df.workshop_type[i], df.building_type.Workshop, i,-1,nil)
|
||||
end
|
||||
end
|
||||
|
||||
self:pushContext('Workshops', choices)
|
||||
end
|
||||
function BuildingDialog:initTrapMode()
|
||||
local choices = {}
|
||||
|
||||
for i=0,df.trap_type._last_item do
|
||||
self:addBuilding(choices, df.trap_type[i], df.building_type.Trap, i,-1,nil)
|
||||
end
|
||||
|
||||
self:pushContext('Traps', choices)
|
||||
end
|
||||
|
||||
function BuildingDialog:initConstructionMode()
|
||||
local choices = {}
|
||||
|
||||
for i=0,df.construction_type._last_item do
|
||||
self:addBuilding(choices, df.construction_type[i], df.building_type.Construction, i,-1,nil)
|
||||
end
|
||||
|
||||
self:pushContext('Constructions', choices)
|
||||
end
|
||||
|
||||
function BuildingDialog:initFurnaceMode()
|
||||
local choices = {}
|
||||
|
||||
for i=0,df.furnace_type._last_item do
|
||||
self:addBuilding(choices, df.furnace_type[i], df.building_type.Furnace, i,-1,nil)
|
||||
end
|
||||
|
||||
self:pushContext('Furnaces', choices)
|
||||
end
|
||||
|
||||
function BuildingDialog:initSiegeMode()
|
||||
local choices = {}
|
||||
|
||||
for i=0,df.siegeengine_type._last_item do
|
||||
self:addBuilding(choices, df.siegeengine_type[i], df.building_type.SiegeEngine, i,-1,nil)
|
||||
end
|
||||
|
||||
self:pushContext('Siege weapons', choices)
|
||||
end
|
||||
function BuildingDialog:initCustomMode()
|
||||
local choices = {}
|
||||
local raws=df.global.world.raws.buildings.all
|
||||
for k,v in pairs(raws) do
|
||||
self:addBuilding(choices, v.name, df.building_type.Workshop,df.workshop_type.Custom,v.id,v)
|
||||
end
|
||||
|
||||
self:pushContext('Custom workshops', choices)
|
||||
end
|
||||
|
||||
function BuildingDialog:addBuilding(choices, name,type_id, subtype_id, custom_id, parent)
|
||||
-- Check the filter
|
||||
if self.building_filter and not self.building_filter(name,type_id,subtype_id,custom_id, parent) then
|
||||
return
|
||||
end
|
||||
|
||||
table.insert(choices, {
|
||||
text = name:lower(),
|
||||
customshop = parent,
|
||||
type_id = type_id, subtype_id = subtype_id, custom_id=custom_id
|
||||
})
|
||||
end
|
||||
|
||||
function BuildingDialog:pushContext(name, choices)
|
||||
if not self.back_stack then
|
||||
self.back_stack = {}
|
||||
self.subviews.back.visible = false
|
||||
else
|
||||
table.insert(self.back_stack, {
|
||||
context_str = self.context_str,
|
||||
all_choices = self.subviews.list:getChoices(),
|
||||
edit_text = self.subviews.list:getFilter(),
|
||||
selected = self.subviews.list:getSelected(),
|
||||
})
|
||||
self.subviews.back.visible = true
|
||||
end
|
||||
|
||||
self.context_str = name
|
||||
self.subviews.list:setChoices(choices, 1)
|
||||
end
|
||||
|
||||
function BuildingDialog:onGoBack()
|
||||
local save = table.remove(self.back_stack)
|
||||
self.subviews.back.visible = (#self.back_stack > 0)
|
||||
|
||||
self.context_str = save.context_str
|
||||
self.subviews.list:setChoices(save.all_choices)
|
||||
self.subviews.list:setFilter(save.edit_text, save.selected)
|
||||
end
|
||||
|
||||
function BuildingDialog:submitBuilding(type_id,subtype_id,custom_id,choice,index)
|
||||
self:dismiss()
|
||||
|
||||
if self.on_select then
|
||||
self.on_select(type_id,subtype_id,custom_id,choice,index)
|
||||
end
|
||||
end
|
||||
|
||||
function BuildingDialog:onSubmitItem(idx, item)
|
||||
if item.cb then
|
||||
item:cb(idx)
|
||||
else
|
||||
self:submitBuilding(item.type_id, item.subtype_id,item.custom_id,item,idx)
|
||||
end
|
||||
end
|
||||
|
||||
function BuildingDialog:onInput(keys)
|
||||
if keys.LEAVESCREEN or keys.LEAVESCREEN_ALL then
|
||||
if self.subviews.back.visible and not keys.LEAVESCREEN_ALL then
|
||||
self:onGoBack()
|
||||
else
|
||||
self:dismiss()
|
||||
if self.on_cancel then
|
||||
self.on_cancel()
|
||||
end
|
||||
end
|
||||
else
|
||||
self:inputToSubviews(keys)
|
||||
end
|
||||
end
|
||||
|
||||
function showBuildingPrompt(title, prompt, on_select, on_cancel, build_filter)
|
||||
BuildingDialog{
|
||||
frame_title = title,
|
||||
prompt = prompt,
|
||||
building_filter = build_filter,
|
||||
on_select = on_select,
|
||||
on_cancel = on_cancel,
|
||||
}:show()
|
||||
end
|
||||
|
||||
return _ENV
|
@ -0,0 +1,337 @@
|
||||
-- Stock dialog for selecting materials
|
||||
|
||||
local _ENV = mkmodule('gui.materials')
|
||||
|
||||
local gui = require('gui')
|
||||
local widgets = require('gui.widgets')
|
||||
local dlg = require('gui.dialogs')
|
||||
local utils = require('utils')
|
||||
|
||||
ARROW = string.char(26)
|
||||
|
||||
CREATURE_BASE = 19
|
||||
PLANT_BASE = 419
|
||||
|
||||
MaterialDialog = defclass(MaterialDialog, gui.FramedScreen)
|
||||
|
||||
MaterialDialog.focus_path = 'MaterialDialog'
|
||||
|
||||
MaterialDialog.ATTRS{
|
||||
prompt = 'Type or select a material from this list',
|
||||
frame_style = gui.GREY_LINE_FRAME,
|
||||
frame_inset = 1,
|
||||
frame_title = 'Select Material',
|
||||
-- new attrs
|
||||
none_caption = 'none',
|
||||
hide_none = false,
|
||||
use_inorganic = true,
|
||||
use_creature = true,
|
||||
use_plant = true,
|
||||
mat_filter = DEFAULT_NIL,
|
||||
on_select = DEFAULT_NIL,
|
||||
on_cancel = DEFAULT_NIL,
|
||||
on_close = DEFAULT_NIL,
|
||||
}
|
||||
|
||||
function MaterialDialog:init(info)
|
||||
self:addviews{
|
||||
widgets.Label{
|
||||
text = {
|
||||
self.prompt, '\n\n',
|
||||
'Category: ', { text = self:cb_getfield('context_str'), pen = COLOR_CYAN }
|
||||
},
|
||||
text_pen = COLOR_WHITE,
|
||||
frame = { l = 0, t = 0 },
|
||||
},
|
||||
widgets.Label{
|
||||
view_id = 'back',
|
||||
visible = false,
|
||||
text = { { key = 'LEAVESCREEN', text = ': Back' } },
|
||||
frame = { r = 0, b = 0 },
|
||||
auto_width = true,
|
||||
},
|
||||
widgets.FilteredList{
|
||||
view_id = 'list',
|
||||
not_found_label = 'No matching materials',
|
||||
frame = { l = 0, r = 0, t = 4, b = 2 },
|
||||
icon_width = 2,
|
||||
on_submit = self:callback('onSubmitItem'),
|
||||
},
|
||||
widgets.Label{
|
||||
text = { {
|
||||
key = 'SELECT', text = ': Select',
|
||||
disabled = function() return not self.subviews.list:canSubmit() end
|
||||
} },
|
||||
frame = { l = 0, b = 0 },
|
||||
}
|
||||
}
|
||||
self:initBuiltinMode()
|
||||
end
|
||||
|
||||
function MaterialDialog:getWantedFrameSize(rect)
|
||||
return math.max(self.frame_width or 40, #self.prompt), math.min(28, rect.height-8)
|
||||
end
|
||||
|
||||
function MaterialDialog:onDestroy()
|
||||
if self.on_close then
|
||||
self.on_close()
|
||||
end
|
||||
end
|
||||
|
||||
function MaterialDialog:initBuiltinMode()
|
||||
local choices = {}
|
||||
if not self.hide_none then
|
||||
table.insert(choices, { text = self.none_caption, mat_type = -1, mat_index = -1 })
|
||||
end
|
||||
|
||||
if self.use_inorganic then
|
||||
table.insert(choices, {
|
||||
icon = ARROW, text = 'inorganic', key = 'CUSTOM_SHIFT_I',
|
||||
cb = self:callback('initInorganicMode')
|
||||
})
|
||||
end
|
||||
if self.use_creature then
|
||||
table.insert(choices, {
|
||||
icon = ARROW, text = 'creature', key = 'CUSTOM_SHIFT_C',
|
||||
cb = self:callback('initCreatureMode')
|
||||
})
|
||||
end
|
||||
if self.use_plant then
|
||||
table.insert(choices, {
|
||||
icon = ARROW, text = 'plant', key = 'CUSTOM_SHIFT_P',
|
||||
cb = self:callback('initPlantMode')
|
||||
})
|
||||
end
|
||||
|
||||
local table = df.global.world.raws.mat_table.builtin
|
||||
for i=0,df.builtin_mats._last_item do
|
||||
self:addMaterial(choices, table[i], i, -1, false, nil)
|
||||
end
|
||||
|
||||
self:pushContext('Any material', choices)
|
||||
end
|
||||
|
||||
function MaterialDialog:initInorganicMode()
|
||||
local choices = {}
|
||||
|
||||
for i,mat in ipairs(df.global.world.raws.inorganics) do
|
||||
self:addMaterial(choices, mat.material, 0, i, false, mat)
|
||||
end
|
||||
|
||||
self:pushContext('Inorganic materials', choices)
|
||||
end
|
||||
|
||||
function MaterialDialog:initCreatureMode()
|
||||
local choices = {}
|
||||
|
||||
for i,v in ipairs(df.global.world.raws.creatures.all) do
|
||||
self:addObjectChoice(choices, v, v.name[0], CREATURE_BASE, i)
|
||||
end
|
||||
|
||||
self:pushContext('Creature materials', choices)
|
||||
end
|
||||
|
||||
function MaterialDialog:initPlantMode()
|
||||
local choices = {}
|
||||
|
||||
for i,v in ipairs(df.global.world.raws.plants.all) do
|
||||
self:addObjectChoice(choices, v, v.name, PLANT_BASE, i)
|
||||
end
|
||||
|
||||
self:pushContext('Plant materials', choices)
|
||||
end
|
||||
|
||||
function MaterialDialog:addObjectChoice(choices, obj, name, typ, index)
|
||||
-- Check if any eligible children
|
||||
local count = #obj.material
|
||||
local idx = 0
|
||||
|
||||
if self.mat_filter then
|
||||
count = 0
|
||||
for i,v in ipairs(obj.material) do
|
||||
if self.mat_filter(v, obj, typ+i, index) then
|
||||
count = count + 1
|
||||
if count > 1 then break end
|
||||
idx = i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Create an entry
|
||||
if count < 1 then
|
||||
return
|
||||
elseif count == 1 then
|
||||
self:addMaterial(choices, obj.material[idx], typ+idx, index, true, obj)
|
||||
else
|
||||
table.insert(choices, {
|
||||
icon = ARROW, text = name, mat_type = typ, mat_index = index,
|
||||
obj = obj, cb = self:callback('onSelectObj')
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
function MaterialDialog:onSelectObj(item)
|
||||
local choices = {}
|
||||
for i,v in ipairs(item.obj.material) do
|
||||
self:addMaterial(choices, v, item.mat_type+i, item.mat_index, false, item.obj)
|
||||
end
|
||||
self:pushContext(item.text, choices)
|
||||
end
|
||||
|
||||
function MaterialDialog:addMaterial(choices, mat, typ, idx, pfix, parent)
|
||||
-- Check the filter
|
||||
if self.mat_filter and not self.mat_filter(mat, parent, typ, idx) then
|
||||
return
|
||||
end
|
||||
|
||||
-- Find the material name
|
||||
local state = 0
|
||||
if mat.heat.melting_point <= 10015 then
|
||||
state = 1
|
||||
end
|
||||
local name = mat.state_name[state]
|
||||
name = string.gsub(name, '^frozen ','')
|
||||
name = string.gsub(name, '^molten ','')
|
||||
name = string.gsub(name, '^condensed ','')
|
||||
|
||||
-- Add prefix if requested
|
||||
local key
|
||||
if pfix and mat.prefix ~= '' then
|
||||
name = mat.prefix .. ' ' .. name
|
||||
key = mat.prefix
|
||||
end
|
||||
|
||||
table.insert(choices, {
|
||||
text = name,
|
||||
search_key = key,
|
||||
material = mat,
|
||||
mat_type = typ, mat_index = idx
|
||||
})
|
||||
end
|
||||
|
||||
function MaterialDialog:pushContext(name, choices)
|
||||
if not self.back_stack then
|
||||
self.back_stack = {}
|
||||
self.subviews.back.visible = false
|
||||
else
|
||||
table.insert(self.back_stack, {
|
||||
context_str = self.context_str,
|
||||
all_choices = self.subviews.list:getChoices(),
|
||||
edit_text = self.subviews.list:getFilter(),
|
||||
selected = self.subviews.list:getSelected(),
|
||||
})
|
||||
self.subviews.back.visible = true
|
||||
end
|
||||
|
||||
self.context_str = name
|
||||
self.subviews.list:setChoices(choices, 1)
|
||||
end
|
||||
|
||||
function MaterialDialog:onGoBack()
|
||||
local save = table.remove(self.back_stack)
|
||||
self.subviews.back.visible = (#self.back_stack > 0)
|
||||
|
||||
self.context_str = save.context_str
|
||||
self.subviews.list:setChoices(save.all_choices)
|
||||
self.subviews.list:setFilter(save.edit_text, save.selected)
|
||||
end
|
||||
|
||||
function MaterialDialog:submitMaterial(typ, index)
|
||||
self:dismiss()
|
||||
|
||||
if self.on_select then
|
||||
self.on_select(typ, index)
|
||||
end
|
||||
end
|
||||
|
||||
function MaterialDialog:onSubmitItem(idx, item)
|
||||
if item.cb then
|
||||
item:cb(idx)
|
||||
else
|
||||
self:submitMaterial(item.mat_type, item.mat_index)
|
||||
end
|
||||
end
|
||||
|
||||
function MaterialDialog:onInput(keys)
|
||||
if keys.LEAVESCREEN or keys.LEAVESCREEN_ALL then
|
||||
if self.subviews.back.visible and not keys.LEAVESCREEN_ALL then
|
||||
self:onGoBack()
|
||||
else
|
||||
self:dismiss()
|
||||
if self.on_cancel then
|
||||
self.on_cancel()
|
||||
end
|
||||
end
|
||||
else
|
||||
self:inputToSubviews(keys)
|
||||
end
|
||||
end
|
||||
|
||||
function showMaterialPrompt(title, prompt, on_select, on_cancel, mat_filter)
|
||||
MaterialDialog{
|
||||
frame_title = title,
|
||||
prompt = prompt,
|
||||
mat_filter = mat_filter,
|
||||
on_select = on_select,
|
||||
on_cancel = on_cancel,
|
||||
}:show()
|
||||
end
|
||||
|
||||
function ItemTypeDialog(args)
|
||||
args.text = args.prompt or 'Type or select an item type'
|
||||
args.text_pen = COLOR_WHITE
|
||||
args.with_filter = true
|
||||
args.icon_width = 2
|
||||
|
||||
local choices = {}
|
||||
|
||||
if not args.hide_none then
|
||||
table.insert(choices, {
|
||||
icon = '?', text = args.none_caption or 'none',
|
||||
item_type = -1, item_subtype = -1
|
||||
})
|
||||
end
|
||||
|
||||
local filter = args.item_filter
|
||||
|
||||
for itype = 0,df.item_type._last_item do
|
||||
local attrs = df.item_type.attrs[itype]
|
||||
local defcnt = dfhack.items.getSubtypeCount(itype)
|
||||
|
||||
if not filter or filter(itype,-1) then
|
||||
local name = attrs.caption or df.item_type[itype]
|
||||
local icon
|
||||
if defcnt >= 0 then
|
||||
name = 'any '..name
|
||||
icon = '+'
|
||||
end
|
||||
table.insert(choices, {
|
||||
icon = icon, text = string.lower(name), item_type = itype, item_subtype = -1
|
||||
})
|
||||
end
|
||||
|
||||
if defcnt > 0 then
|
||||
for subtype = 0,defcnt-1 do
|
||||
local def = dfhack.items.getSubtypeDef(itype, subtype)
|
||||
if not filter or filter(itype,subtype,def) then
|
||||
table.insert(choices, {
|
||||
icon = '\x1e', text = ' '..def.name, item_type = itype, item_subtype = subtype
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
args.choices = choices
|
||||
|
||||
if args.on_select then
|
||||
local cb = args.on_select
|
||||
args.on_select = function(idx, obj)
|
||||
return cb(obj.item_type, obj.item_subtype)
|
||||
end
|
||||
end
|
||||
|
||||
return dlg.ListBox(args)
|
||||
end
|
||||
|
||||
return _ENV
|
@ -0,0 +1,164 @@
|
||||
-- Support for scripted interaction sequences via coroutines.
|
||||
|
||||
local _ENV = mkmodule('gui.script')
|
||||
|
||||
local dlg = require('gui.dialogs')
|
||||
|
||||
--[[
|
||||
Example:
|
||||
|
||||
start(function()
|
||||
sleep(100, 'frames')
|
||||
print(showYesNoPrompt('test', 'print true?'))
|
||||
end)
|
||||
]]
|
||||
|
||||
-- Table of running background scripts.
|
||||
if not scripts then
|
||||
scripts = {}
|
||||
setmetatable(scripts, { __mode = 'k' })
|
||||
end
|
||||
|
||||
local function do_resume(inst, ...)
|
||||
inst.gen = inst.gen + 1
|
||||
return (dfhack.saferesume(inst.coro, ...))
|
||||
end
|
||||
|
||||
-- Starts a new background script by calling the function.
|
||||
function start(fn,...)
|
||||
local coro = coroutine.create(fn)
|
||||
local inst = {
|
||||
coro = coro,
|
||||
gen = 0,
|
||||
}
|
||||
scripts[coro] = inst
|
||||
return do_resume(inst, ...)
|
||||
end
|
||||
|
||||
-- Checks if called from a background script
|
||||
function in_script()
|
||||
return scripts[coroutine.running()] ~= nil
|
||||
end
|
||||
|
||||
local function getinst()
|
||||
local inst = scripts[coroutine.running()]
|
||||
if not inst then
|
||||
error('Not in a gui script coroutine.')
|
||||
end
|
||||
return inst
|
||||
end
|
||||
|
||||
local function invoke_resume(inst,gen,quiet,...)
|
||||
local state = coroutine.status(inst.coro)
|
||||
if state ~= 'suspended' then
|
||||
if state ~= 'dead' then
|
||||
dfhack.printerr(debug.traceback('resuming a non-waiting continuation'))
|
||||
end
|
||||
elseif inst.gen > gen then
|
||||
if not quiet then
|
||||
dfhack.printerr(debug.traceback('resuming an expired continuation'))
|
||||
end
|
||||
else
|
||||
do_resume(inst, ...)
|
||||
end
|
||||
end
|
||||
|
||||
-- Returns a callback that resumes the script from wait with given return values
|
||||
function mkresume(...)
|
||||
local inst = getinst()
|
||||
return curry(invoke_resume, inst, inst.gen, false, ...)
|
||||
end
|
||||
|
||||
-- Like mkresume, but does not complain if already resumed from this wait
|
||||
function qresume(...)
|
||||
local inst = getinst()
|
||||
return curry(invoke_resume, inst, inst.gen, true, ...)
|
||||
end
|
||||
|
||||
-- Wait until a mkresume callback is called, then return its arguments.
|
||||
-- Once it returns, all mkresume callbacks created before are invalidated.
|
||||
function wait()
|
||||
getinst() -- check that the context is right
|
||||
return coroutine.yield()
|
||||
end
|
||||
|
||||
-- Wraps dfhack.timeout for coroutines.
|
||||
function sleep(time, quantity)
|
||||
if dfhack.timeout(time, quantity, mkresume()) then
|
||||
wait()
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- Some dialog wrappers:
|
||||
|
||||
function showMessage(title, text, tcolor)
|
||||
dlg.MessageBox{
|
||||
frame_title = title,
|
||||
text = text,
|
||||
text_pen = tcolor,
|
||||
on_close = qresume(nil)
|
||||
}:show()
|
||||
|
||||
return wait()
|
||||
end
|
||||
|
||||
function showYesNoPrompt(title, text, tcolor)
|
||||
dlg.MessageBox{
|
||||
frame_title = title,
|
||||
text = text,
|
||||
text_pen = tcolor,
|
||||
on_accept = mkresume(true),
|
||||
on_cancel = mkresume(false),
|
||||
on_close = qresume(nil)
|
||||
}:show()
|
||||
|
||||
return wait()
|
||||
end
|
||||
|
||||
function showInputPrompt(title, text, tcolor, input, min_width)
|
||||
dlg.InputBox{
|
||||
frame_title = title,
|
||||
text = text,
|
||||
text_pen = tcolor,
|
||||
input = input,
|
||||
frame_width = min_width,
|
||||
on_input = mkresume(true),
|
||||
on_cancel = mkresume(false),
|
||||
on_close = qresume(nil)
|
||||
}:show()
|
||||
|
||||
return wait()
|
||||
end
|
||||
|
||||
function showListPrompt(title, text, tcolor, choices, min_width, filter)
|
||||
dlg.ListBox{
|
||||
frame_title = title,
|
||||
text = text,
|
||||
text_pen = tcolor,
|
||||
choices = choices,
|
||||
frame_width = min_width,
|
||||
with_filter = filter,
|
||||
on_select = mkresume(true),
|
||||
on_cancel = mkresume(false),
|
||||
on_close = qresume(nil)
|
||||
}:show()
|
||||
|
||||
return wait()
|
||||
end
|
||||
|
||||
function showMaterialPrompt(title, prompt)
|
||||
require('gui.materials').MaterialDialog{
|
||||
frame_title = title,
|
||||
prompt = prompt,
|
||||
on_select = mkresume(true,
|
||||
on_cancel = mkresume(false),
|
||||
on_close = qresume(nil)
|
||||
}:show()
|
||||
|
||||
return wait()
|
||||
end
|
||||
|
||||
return _ENV
|
@ -0,0 +1,783 @@
|
||||
-- Simple widgets for screens
|
||||
|
||||
local _ENV = mkmodule('gui.widgets')
|
||||
|
||||
local gui = require('gui')
|
||||
local utils = require('utils')
|
||||
|
||||
local dscreen = dfhack.screen
|
||||
|
||||
local function show_view(view,vis)
|
||||
if view then
|
||||
view.visible = vis
|
||||
end
|
||||
end
|
||||
|
||||
local function getval(obj)
|
||||
if type(obj) == 'function' then
|
||||
return obj()
|
||||
else
|
||||
return obj
|
||||
end
|
||||
end
|
||||
|
||||
local function map_opttab(tab,idx)
|
||||
if tab then
|
||||
return tab[idx]
|
||||
else
|
||||
return idx
|
||||
end
|
||||
end
|
||||
|
||||
------------
|
||||
-- Widget --
|
||||
------------
|
||||
|
||||
Widget = defclass(Widget, gui.View)
|
||||
|
||||
Widget.ATTRS {
|
||||
frame = DEFAULT_NIL,
|
||||
frame_inset = DEFAULT_NIL,
|
||||
frame_background = DEFAULT_NIL,
|
||||
}
|
||||
|
||||
function Widget:computeFrame(parent_rect)
|
||||
local sw, sh = parent_rect.width, parent_rect.height
|
||||
return gui.compute_frame_body(sw, sh, self.frame, self.frame_inset)
|
||||
end
|
||||
|
||||
function Widget:onRenderFrame(dc, rect)
|
||||
if self.frame_background then
|
||||
dc:fill(rect, self.frame_background)
|
||||
end
|
||||
end
|
||||
|
||||
-----------
|
||||
-- Panel --
|
||||
-----------
|
||||
|
||||
Panel = defclass(Panel, Widget)
|
||||
|
||||
Panel.ATTRS {
|
||||
on_render = DEFAULT_NIL,
|
||||
on_layout = DEFAULT_NIL,
|
||||
}
|
||||
|
||||
function Panel:init(args)
|
||||
self:addviews(args.subviews)
|
||||
end
|
||||
|
||||
function Panel:onRenderBody(dc)
|
||||
if self.on_render then self.on_render(dc) end
|
||||
end
|
||||
|
||||
function Panel:postComputeFrame(body)
|
||||
if self.on_layout then self.on_layout(body) end
|
||||
end
|
||||
|
||||
-----------
|
||||
-- Pages --
|
||||
-----------
|
||||
|
||||
Pages = defclass(Pages, Panel)
|
||||
|
||||
function Pages:init(args)
|
||||
for _,v in ipairs(self.subviews) do
|
||||
v.visible = false
|
||||
end
|
||||
self:setSelected(args.selected or 1)
|
||||
end
|
||||
|
||||
function Pages:setSelected(idx)
|
||||
if type(idx) ~= 'number' then
|
||||
local key = idx
|
||||
if type(idx) == 'string' then
|
||||
key = self.subviews[key]
|
||||
end
|
||||
idx = utils.linear_index(self.subviews, key)
|
||||
if not idx then
|
||||
error('Unknown page: '..key)
|
||||
end
|
||||
end
|
||||
|
||||
show_view(self.subviews[self.selected], false)
|
||||
self.selected = math.min(math.max(1, idx), #self.subviews)
|
||||
show_view(self.subviews[self.selected], true)
|
||||
end
|
||||
|
||||
function Pages:getSelected()
|
||||
return self.selected, self.subviews[self.selected]
|
||||
end
|
||||
|
||||
----------------
|
||||
-- Edit field --
|
||||
----------------
|
||||
|
||||
EditField = defclass(EditField, Widget)
|
||||
|
||||
EditField.ATTRS{
|
||||
text = '',
|
||||
text_pen = DEFAULT_NIL,
|
||||
on_char = DEFAULT_NIL,
|
||||
on_change = DEFAULT_NIL,
|
||||
on_submit = DEFAULT_NIL,
|
||||
}
|
||||
|
||||
function EditField:onRenderBody(dc)
|
||||
dc:pen(self.text_pen or COLOR_LIGHTCYAN):fill(0,0,dc.width-1,0)
|
||||
|
||||
local cursor = '_'
|
||||
if not self.active or gui.blink_visible(300) then
|
||||
cursor = ' '
|
||||
end
|
||||
local txt = self.text .. cursor
|
||||
if #txt > dc.width then
|
||||
txt = string.char(27)..string.sub(txt, #txt-dc.width+2)
|
||||
end
|
||||
dc:string(txt)
|
||||
end
|
||||
|
||||
function EditField:onInput(keys)
|
||||
if self.on_submit and keys.SELECT then
|
||||
self.on_submit(self.text)
|
||||
return true
|
||||
elseif keys._STRING then
|
||||
local old = self.text
|
||||
if keys._STRING == 0 then
|
||||
self.text = string.sub(old, 1, #old-1)
|
||||
else
|
||||
local cv = string.char(keys._STRING)
|
||||
if not self.on_char or self.on_char(cv, old) then
|
||||
self.text = old .. cv
|
||||
end
|
||||
end
|
||||
if self.on_change and self.text ~= old then
|
||||
self.on_change(self.text, old)
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-----------
|
||||
-- Label --
|
||||
-----------
|
||||
|
||||
function parse_label_text(obj)
|
||||
local text = obj.text or {}
|
||||
if type(text) ~= 'table' then
|
||||
text = { text }
|
||||
end
|
||||
local curline = nil
|
||||
local out = { }
|
||||
local active = nil
|
||||
local idtab = nil
|
||||
for _,v in ipairs(text) do
|
||||
local vv
|
||||
if type(v) == 'string' then
|
||||
vv = utils.split_string(v, NEWLINE)
|
||||
else
|
||||
vv = { v }
|
||||
end
|
||||
|
||||
for i = 1,#vv do
|
||||
local cv = vv[i]
|
||||
if i > 1 then
|
||||
if not curline then
|
||||
table.insert(out, {})
|
||||
end
|
||||
curline = nil
|
||||
end
|
||||
if cv ~= '' then
|
||||
if not curline then
|
||||
curline = {}
|
||||
table.insert(out, curline)
|
||||
end
|
||||
|
||||
if type(cv) == 'string' then
|
||||
table.insert(curline, { text = cv })
|
||||
else
|
||||
table.insert(curline, cv)
|
||||
|
||||
if cv.on_activate then
|
||||
active = active or {}
|
||||
table.insert(active, cv)
|
||||
end
|
||||
|
||||
if cv.id then
|
||||
idtab = idtab or {}
|
||||
idtab[cv.id] = cv
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
obj.text_lines = out
|
||||
obj.text_active = active
|
||||
obj.text_ids = idtab
|
||||
end
|
||||
|
||||
local function is_disabled(token)
|
||||
return (token.disabled ~= nil and getval(token.disabled)) or
|
||||
(token.enabled ~= nil and not getval(token.enabled))
|
||||
end
|
||||
|
||||
function render_text(obj,dc,x0,y0,pen,dpen,disabled)
|
||||
local width = 0
|
||||
for iline,line in ipairs(obj.text_lines) do
|
||||
local x = 0
|
||||
if dc then
|
||||
dc:seek(x+x0,y0+iline-1)
|
||||
end
|
||||
for _,token in ipairs(line) do
|
||||
token.line = iline
|
||||
token.x1 = x
|
||||
|
||||
if token.gap then
|
||||
x = x + token.gap
|
||||
if dc then
|
||||
dc:advance(token.gap)
|
||||
end
|
||||
end
|
||||
|
||||
if token.tile then
|
||||
x = x + 1
|
||||
if dc then
|
||||
dc:char(nil, token.tile)
|
||||
end
|
||||
end
|
||||
|
||||
if token.text or token.key then
|
||||
local text = ''..(getval(token.text) or '')
|
||||
local keypen
|
||||
|
||||
if dc then
|
||||
local tpen = getval(token.pen)
|
||||
if disabled or is_disabled(token) then
|
||||
dc:pen(getval(token.dpen) or tpen or dpen)
|
||||
keypen = COLOR_GREEN
|
||||
else
|
||||
dc:pen(tpen or pen)
|
||||
keypen = COLOR_LIGHTGREEN
|
||||
end
|
||||
end
|
||||
|
||||
local width = getval(token.width)
|
||||
local padstr
|
||||
if width then
|
||||
x = x + width
|
||||
if #text > width then
|
||||
text = string.sub(text,1,width)
|
||||
else
|
||||
if token.pad_char then
|
||||
padstr = string.rep(token.pad_char,width-#text)
|
||||
end
|
||||
if dc and token.rjustify then
|
||||
if padstr then dc:string(padstr) else dc:advance(width-#text) end
|
||||
end
|
||||
end
|
||||
else
|
||||
x = x + #text
|
||||
end
|
||||
|
||||
if token.key then
|
||||
local keystr = gui.getKeyDisplay(token.key)
|
||||
local sep = token.key_sep or ''
|
||||
|
||||
x = x + #keystr
|
||||
|
||||
if sep == '()' then
|
||||
if dc then
|
||||
dc:string(text)
|
||||
dc:string(' ('):string(keystr,keypen):string(')')
|
||||
end
|
||||
x = x + 3
|
||||
else
|
||||
if dc then
|
||||
dc:string(keystr,keypen):string(sep):string(text)
|
||||
end
|
||||
x = x + #sep
|
||||
end
|
||||
else
|
||||
if dc then
|
||||
dc:string(text)
|
||||
end
|
||||
end
|
||||
|
||||
if width and dc and not token.rjustify then
|
||||
if padstr then dc:string(padstr) else dc:advance(width-#text) end
|
||||
end
|
||||
end
|
||||
|
||||
token.x2 = x
|
||||
end
|
||||
width = math.max(width, x)
|
||||
end
|
||||
obj.text_width = width
|
||||
end
|
||||
|
||||
function check_text_keys(self, keys)
|
||||
if self.text_active then
|
||||
for _,item in ipairs(self.text_active) do
|
||||
if item.key and keys[item.key] and not is_disabled(item) then
|
||||
item.on_activate()
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Label = defclass(Label, Widget)
|
||||
|
||||
Label.ATTRS{
|
||||
text_pen = COLOR_WHITE,
|
||||
text_dpen = COLOR_DARKGREY,
|
||||
disabled = DEFAULT_NIL,
|
||||
enabled = DEFAULT_NIL,
|
||||
auto_height = true,
|
||||
auto_width = false,
|
||||
}
|
||||
|
||||
function Label:init(args)
|
||||
self:setText(args.text)
|
||||
end
|
||||
|
||||
function Label:setText(text)
|
||||
self.text = text
|
||||
parse_label_text(self)
|
||||
|
||||
if self.auto_height then
|
||||
self.frame = self.frame or {}
|
||||
self.frame.h = self:getTextHeight()
|
||||
end
|
||||
end
|
||||
|
||||
function Label:preUpdateLayout()
|
||||
if self.auto_width then
|
||||
self.frame = self.frame or {}
|
||||
self.frame.w = self:getTextWidth()
|
||||
end
|
||||
end
|
||||
|
||||
function Label:itemById(id)
|
||||
if self.text_ids then
|
||||
return self.text_ids[id]
|
||||
end
|
||||
end
|
||||
|
||||
function Label:getTextHeight()
|
||||
return #self.text_lines
|
||||
end
|
||||
|
||||
function Label:getTextWidth()
|
||||
render_text(self)
|
||||
return self.text_width
|
||||
end
|
||||
|
||||
function Label:onRenderBody(dc)
|
||||
render_text(self,dc,0,0,self.text_pen,self.text_dpen,is_disabled(self))
|
||||
end
|
||||
|
||||
function Label:onInput(keys)
|
||||
if not is_disabled(self) then
|
||||
return check_text_keys(self, keys)
|
||||
end
|
||||
end
|
||||
|
||||
----------
|
||||
-- List --
|
||||
----------
|
||||
|
||||
List = defclass(List, Widget)
|
||||
|
||||
STANDARDSCROLL = {
|
||||
STANDARDSCROLL_UP = -1,
|
||||
STANDARDSCROLL_DOWN = 1,
|
||||
STANDARDSCROLL_PAGEUP = '-page',
|
||||
STANDARDSCROLL_PAGEDOWN = '+page',
|
||||
}
|
||||
|
||||
SECONDSCROLL = {
|
||||
SECONDSCROLL_UP = -1,
|
||||
SECONDSCROLL_DOWN = 1,
|
||||
SECONDSCROLL_PAGEUP = '-page',
|
||||
SECONDSCROLL_PAGEDOWN = '+page',
|
||||
}
|
||||
|
||||
List.ATTRS{
|
||||
text_pen = COLOR_CYAN,
|
||||
cursor_pen = COLOR_LIGHTCYAN,
|
||||
inactive_pen = DEFAULT_NIL,
|
||||
on_select = DEFAULT_NIL,
|
||||
on_submit = DEFAULT_NIL,
|
||||
on_submit2 = DEFAULT_NIL,
|
||||
row_height = 1,
|
||||
scroll_keys = STANDARDSCROLL,
|
||||
icon_width = DEFAULT_NIL,
|
||||
}
|
||||
|
||||
function List:init(info)
|
||||
self.page_top = 1
|
||||
self.page_size = 1
|
||||
|
||||
if info.choices then
|
||||
self:setChoices(info.choices, info.selected)
|
||||
else
|
||||
self.choices = {}
|
||||
self.selected = 1
|
||||
end
|
||||
end
|
||||
|
||||
function List:setChoices(choices, selected)
|
||||
self.choices = choices or {}
|
||||
|
||||
for i,v in ipairs(self.choices) do
|
||||
if type(v) ~= 'table' then
|
||||
v = { text = v }
|
||||
self.choices[i] = v
|
||||
end
|
||||
v.text = v.text or v.caption or v[1]
|
||||
parse_label_text(v)
|
||||
end
|
||||
|
||||
self:setSelected(selected)
|
||||
end
|
||||
|
||||
function List:setSelected(selected)
|
||||
self.selected = selected or self.selected or 1
|
||||
self:moveCursor(0, true)
|
||||
return self.selected
|
||||
end
|
||||
|
||||
function List:getChoices()
|
||||
return self.choices
|
||||
end
|
||||
|
||||
function List:getSelected()
|
||||
if #self.choices > 0 then
|
||||
return self.selected, self.choices[self.selected]
|
||||
end
|
||||
end
|
||||
|
||||
function List:getContentWidth()
|
||||
local width = 0
|
||||
for i,v in ipairs(self.choices) do
|
||||
render_text(v)
|
||||
local roww = v.text_width
|
||||
if v.key then
|
||||
roww = roww + 3 + #gui.getKeyDisplay(v.key)
|
||||
end
|
||||
width = math.max(width, roww)
|
||||
end
|
||||
return width + (self.icon_width or 0)
|
||||
end
|
||||
|
||||
function List:getContentHeight()
|
||||
return #self.choices * self.row_height
|
||||
end
|
||||
|
||||
function List:postComputeFrame(body)
|
||||
self.page_size = math.max(1, math.floor(body.height / self.row_height))
|
||||
self:moveCursor(0)
|
||||
end
|
||||
|
||||
function List:moveCursor(delta, force_cb)
|
||||
local page = math.max(1, self.page_size)
|
||||
local cnt = #self.choices
|
||||
|
||||
if cnt < 1 then
|
||||
self.page_top = 1
|
||||
self.selected = 1
|
||||
if force_cb and self.on_select then
|
||||
self.on_select(nil,nil)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
local off = self.selected+delta-1
|
||||
local ds = math.abs(delta)
|
||||
|
||||
if ds > 1 then
|
||||
if off >= cnt+ds-1 then
|
||||
off = 0
|
||||
else
|
||||
off = math.min(cnt-1, off)
|
||||
end
|
||||
if off <= -ds then
|
||||
off = cnt-1
|
||||
else
|
||||
off = math.max(0, off)
|
||||
end
|
||||
end
|
||||
|
||||
self.selected = 1 + off % cnt
|
||||
self.page_top = 1 + page * math.floor((self.selected-1) / page)
|
||||
|
||||
if (force_cb or delta ~= 0) and self.on_select then
|
||||
self.on_select(self:getSelected())
|
||||
end
|
||||
end
|
||||
|
||||
function List:onRenderBody(dc)
|
||||
local choices = self.choices
|
||||
local top = self.page_top
|
||||
local iend = math.min(#choices, top+self.page_size-1)
|
||||
local iw = self.icon_width
|
||||
|
||||
local function paint_icon(icon, obj)
|
||||
if type(icon) ~= 'string' then
|
||||
dc:char(nil,icon)
|
||||
else
|
||||
if current then
|
||||
dc:string(icon, obj.icon_pen or self.icon_pen or cur_pen)
|
||||
else
|
||||
dc:string(icon, obj.icon_pen or self.icon_pen or cur_dpen)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for i = top,iend do
|
||||
local obj = choices[i]
|
||||
local current = (i == self.selected)
|
||||
local cur_pen = self.cursor_pen
|
||||
local cur_dpen = self.text_pen
|
||||
|
||||
if not self.active then
|
||||
cur_pen = self.inactive_pen or self.cursor_pen
|
||||
end
|
||||
|
||||
local y = (i - top)*self.row_height
|
||||
local icon = getval(obj.icon)
|
||||
|
||||
if iw and icon then
|
||||
dc:seek(0, y)
|
||||
paint_icon(icon, obj)
|
||||
end
|
||||
|
||||
render_text(obj, dc, iw or 0, y, cur_pen, cur_dpen, not current)
|
||||
|
||||
local ip = dc.width
|
||||
|
||||
if obj.key then
|
||||
local keystr = gui.getKeyDisplay(obj.key)
|
||||
ip = ip-2-#keystr
|
||||
dc:seek(ip,y):pen(self.text_pen)
|
||||
dc:string('('):string(keystr,COLOR_LIGHTGREEN):string(')')
|
||||
end
|
||||
|
||||
if icon and not iw then
|
||||
dc:seek(ip-1,y)
|
||||
paint_icon(icon, obj)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function List:submit()
|
||||
if self.on_submit and #self.choices > 0 then
|
||||
self.on_submit(self:getSelected())
|
||||
end
|
||||
end
|
||||
|
||||
function List:submit2()
|
||||
if self.on_submit2 and #self.choices > 0 then
|
||||
self.on_submit2(self:getSelected())
|
||||
end
|
||||
end
|
||||
|
||||
function List:onInput(keys)
|
||||
if self.on_submit and keys.SELECT then
|
||||
self:submit()
|
||||
return true
|
||||
elseif self.on_submit2 and keys.SEC_SELECT then
|
||||
self:submit2()
|
||||
return true
|
||||
else
|
||||
for k,v in pairs(self.scroll_keys) do
|
||||
if keys[k] then
|
||||
if v == '+page' then
|
||||
v = self.page_size
|
||||
elseif v == '-page' then
|
||||
v = -self.page_size
|
||||
end
|
||||
|
||||
self:moveCursor(v)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
for i,v in ipairs(self.choices) do
|
||||
if v.key and keys[v.key] then
|
||||
self:setSelected(i)
|
||||
self:submit()
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
local current = self.choices[self.selected]
|
||||
if current then
|
||||
return check_text_keys(current, keys)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-------------------
|
||||
-- Filtered List --
|
||||
-------------------
|
||||
|
||||
FilteredList = defclass(FilteredList, Widget)
|
||||
|
||||
FilteredList.ATTRS {
|
||||
edit_below = false,
|
||||
}
|
||||
|
||||
function FilteredList:init(info)
|
||||
self.edit = EditField{
|
||||
text_pen = info.edit_pen or info.cursor_pen,
|
||||
frame = { l = info.icon_width, t = 0, h = 1 },
|
||||
on_change = self:callback('onFilterChange'),
|
||||
on_char = self:callback('onFilterChar'),
|
||||
}
|
||||
self.list = List{
|
||||
frame = { t = 2 },
|
||||
text_pen = info.text_pen,
|
||||
cursor_pen = info.cursor_pen,
|
||||
inactive_pen = info.inactive_pen,
|
||||
icon_pen = info.icon_pen,
|
||||
row_height = info.row_height,
|
||||
scroll_keys = info.scroll_keys,
|
||||
icon_width = info.icon_width,
|
||||
}
|
||||
if self.edit_below then
|
||||
self.edit.frame = { l = info.icon_width, b = 0, h = 1 }
|
||||
self.list.frame = { t = 0, b = 2 }
|
||||
end
|
||||
if info.on_select then
|
||||
self.list.on_select = function()
|
||||
return info.on_select(self:getSelected())
|
||||
end
|
||||
end
|
||||
if info.on_submit then
|
||||
self.list.on_submit = function()
|
||||
return info.on_submit(self:getSelected())
|
||||
end
|
||||
end
|
||||
if info.on_submit2 then
|
||||
self.list.on_submit2 = function()
|
||||
return info.on_submit2(self:getSelected())
|
||||
end
|
||||
end
|
||||
self.not_found = Label{
|
||||
visible = true,
|
||||
text = info.not_found_label or 'No matches',
|
||||
text_pen = COLOR_LIGHTRED,
|
||||
frame = { l = info.icon_width, t = self.list.frame.t },
|
||||
}
|
||||
self:addviews{ self.edit, self.list, self.not_found }
|
||||
if info.choices then
|
||||
self:setChoices(info.choices, info.selected)
|
||||
else
|
||||
self.choices = {}
|
||||
end
|
||||
end
|
||||
|
||||
function FilteredList:getChoices()
|
||||
return self.choices
|
||||
end
|
||||
|
||||
function FilteredList:setChoices(choices, pos)
|
||||
choices = choices or {}
|
||||
self.choices = choices
|
||||
self.edit.text = ''
|
||||
self.list:setChoices(choices, pos)
|
||||
self.not_found.visible = (#choices == 0)
|
||||
end
|
||||
|
||||
function FilteredList:submit()
|
||||
return self.list:submit()
|
||||
end
|
||||
|
||||
function FilteredList:submit2()
|
||||
return self.list:submit2()
|
||||
end
|
||||
|
||||
function FilteredList:canSubmit()
|
||||
return not self.not_found.visible
|
||||
end
|
||||
|
||||
function FilteredList:getSelected()
|
||||
local i,v = self.list:getSelected()
|
||||
if i then
|
||||
return map_opttab(self.choice_index, i), v
|
||||
end
|
||||
end
|
||||
|
||||
function FilteredList:getContentWidth()
|
||||
return self.list:getContentWidth()
|
||||
end
|
||||
|
||||
function FilteredList:getContentHeight()
|
||||
return self.list:getContentHeight() + 2
|
||||
end
|
||||
|
||||
function FilteredList:getFilter()
|
||||
return self.edit.text, self.list.choices
|
||||
end
|
||||
|
||||
function FilteredList:setFilter(filter, pos)
|
||||
local choices = self.choices
|
||||
local cidx = nil
|
||||
|
||||
filter = filter or ''
|
||||
self.edit.text = filter
|
||||
|
||||
if filter ~= '' then
|
||||
local tokens = utils.split_string(filter, ' ')
|
||||
local ipos = pos
|
||||
|
||||
choices = {}
|
||||
cidx = {}
|
||||
pos = nil
|
||||
|
||||
for i,v in ipairs(self.choices) do
|
||||
local ok = true
|
||||
local search_key = v.search_key or v.text
|
||||
for _,key in ipairs(tokens) do
|
||||
if key ~= '' and not string.match(search_key, '%f[^%s\x00]'..key) then
|
||||
ok = false
|
||||
break
|
||||
end
|
||||
end
|
||||
if ok then
|
||||
table.insert(choices, v)
|
||||
cidx[#choices] = i
|
||||
if ipos == i then
|
||||
pos = #choices
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
self.choice_index = cidx
|
||||
self.list:setChoices(choices, pos)
|
||||
self.not_found.visible = (#choices == 0)
|
||||
end
|
||||
|
||||
function FilteredList:onFilterChange(text)
|
||||
self:setFilter(text)
|
||||
end
|
||||
|
||||
local bad_chars = {
|
||||
['%'] = true, ['.'] = true, ['+'] = true, ['*'] = true,
|
||||
['['] = true, [']'] = true, ['('] = true, [')'] = true,
|
||||
}
|
||||
|
||||
function FilteredList:onFilterChar(char, text)
|
||||
if bad_chars[char] then
|
||||
return false
|
||||
end
|
||||
if char == ' ' then
|
||||
return string.match(text, '%S$')
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
return _ENV
|
@ -0,0 +1,503 @@
|
||||
#include "Core.h"
|
||||
#include "Console.h"
|
||||
#include "modules/Buildings.h"
|
||||
#include "modules/Constructions.h"
|
||||
#include "modules/EventManager.h"
|
||||
#include "modules/Job.h"
|
||||
#include "modules/World.h"
|
||||
|
||||
#include "df/building.h"
|
||||
#include "df/construction.h"
|
||||
#include "df/global_objects.h"
|
||||
#include "df/item.h"
|
||||
#include "df/job.h"
|
||||
#include "df/job_list_link.h"
|
||||
#include "df/ui.h"
|
||||
#include "df/unit.h"
|
||||
#include "df/unit_syndrome.h"
|
||||
#include "df/world.h"
|
||||
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
using namespace std;
|
||||
using namespace DFHack;
|
||||
using namespace EventManager;
|
||||
|
||||
/*
|
||||
* TODO:
|
||||
* error checking
|
||||
* consider a typedef instead of a struct for EventHandler
|
||||
**/
|
||||
|
||||
//map<uint32_t, vector<DFHack::EventManager::EventHandler> > tickQueue;
|
||||
multimap<uint32_t, EventHandler> tickQueue;
|
||||
|
||||
//TODO: consider unordered_map of pairs, or unordered_map of unordered_set, or whatever
|
||||
multimap<Plugin*, EventHandler> handlers[EventType::EVENT_MAX];
|
||||
uint32_t eventLastTick[EventType::EVENT_MAX];
|
||||
|
||||
const uint32_t ticksPerYear = 403200;
|
||||
|
||||
void DFHack::EventManager::registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin) {
|
||||
handlers[e].insert(pair<Plugin*, EventHandler>(plugin, handler));
|
||||
}
|
||||
|
||||
void DFHack::EventManager::registerTick(EventHandler handler, int32_t when, Plugin* plugin, bool absolute) {
|
||||
uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear
|
||||
+ DFHack::World::ReadCurrentTick();
|
||||
if ( !Core::getInstance().isWorldLoaded() ) {
|
||||
tick = 0;
|
||||
if ( absolute ) {
|
||||
Core::getInstance().getConsole().print("Warning: absolute flag will not be honored.\n");
|
||||
}
|
||||
}
|
||||
if ( absolute ) {
|
||||
tick = 0;
|
||||
}
|
||||
|
||||
tickQueue.insert(pair<uint32_t, EventHandler>(tick+(uint32_t)when, handler));
|
||||
handlers[EventType::TICK].insert(pair<Plugin*,EventHandler>(plugin,handler));
|
||||
return;
|
||||
}
|
||||
|
||||
void DFHack::EventManager::unregister(EventType::EventType e, EventHandler handler, Plugin* plugin) {
|
||||
for ( multimap<Plugin*, EventHandler>::iterator i = handlers[e].find(plugin); i != handlers[e].end(); i++ ) {
|
||||
if ( (*i).first != plugin )
|
||||
break;
|
||||
EventHandler handle = (*i).second;
|
||||
if ( handle == handler ) {
|
||||
handlers[e].erase(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void DFHack::EventManager::unregisterAll(Plugin* plugin) {
|
||||
for ( auto i = handlers[EventType::TICK].find(plugin); i != handlers[EventType::TICK].end(); i++ ) {
|
||||
if ( (*i).first != plugin )
|
||||
break;
|
||||
|
||||
//shenanigans to avoid concurrent modification
|
||||
EventHandler getRidOf = (*i).second;
|
||||
bool didSomething;
|
||||
do {
|
||||
didSomething = false;
|
||||
for ( auto j = tickQueue.begin(); j != tickQueue.end(); j++ ) {
|
||||
EventHandler candidate = (*j).second;
|
||||
if ( getRidOf != candidate )
|
||||
continue;
|
||||
tickQueue.erase(j);
|
||||
didSomething = true;
|
||||
break;
|
||||
}
|
||||
} while(didSomething);
|
||||
}
|
||||
for ( size_t a = 0; a < (size_t)EventType::EVENT_MAX; a++ ) {
|
||||
handlers[a].erase(plugin);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
static void manageTickEvent(color_ostream& out);
|
||||
static void manageJobInitiatedEvent(color_ostream& out);
|
||||
static void manageJobCompletedEvent(color_ostream& out);
|
||||
static void manageUnitDeathEvent(color_ostream& out);
|
||||
static void manageItemCreationEvent(color_ostream& out);
|
||||
static void manageBuildingEvent(color_ostream& out);
|
||||
static void manageConstructionEvent(color_ostream& out);
|
||||
static void manageSyndromeEvent(color_ostream& out);
|
||||
static void manageInvasionEvent(color_ostream& out);
|
||||
|
||||
//tick event
|
||||
static uint32_t lastTick = 0;
|
||||
|
||||
//job initiated
|
||||
static int32_t lastJobId = -1;
|
||||
|
||||
//job completed
|
||||
static unordered_map<int32_t, df::job*> prevJobs;
|
||||
|
||||
//unit death
|
||||
static unordered_set<int32_t> livingUnits;
|
||||
|
||||
//item creation
|
||||
static int32_t nextItem;
|
||||
|
||||
//building
|
||||
static int32_t nextBuilding;
|
||||
static unordered_set<int32_t> buildings;
|
||||
|
||||
//construction
|
||||
static unordered_set<df::construction*> constructions;
|
||||
static bool gameLoaded;
|
||||
|
||||
//invasion
|
||||
static int32_t nextInvasion;
|
||||
|
||||
void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event event) {
|
||||
static bool doOnce = false;
|
||||
if ( !doOnce ) {
|
||||
//TODO: put this somewhere else
|
||||
doOnce = true;
|
||||
EventHandler buildingHandler(Buildings::updateBuildings, 100);
|
||||
DFHack::EventManager::registerListener(EventType::BUILDING, buildingHandler, NULL);
|
||||
//out.print("Registered listeners.\n %d", __LINE__);
|
||||
}
|
||||
if ( event == DFHack::SC_WORLD_UNLOADED ) {
|
||||
lastTick = 0;
|
||||
lastJobId = -1;
|
||||
for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) {
|
||||
Job::deleteJobStruct((*i).second);
|
||||
}
|
||||
prevJobs.clear();
|
||||
tickQueue.clear();
|
||||
livingUnits.clear();
|
||||
nextItem = -1;
|
||||
nextBuilding = -1;
|
||||
buildings.clear();
|
||||
constructions.clear();
|
||||
|
||||
Buildings::clearBuildings(out);
|
||||
gameLoaded = false;
|
||||
nextInvasion = -1;
|
||||
} else if ( event == DFHack::SC_WORLD_LOADED ) {
|
||||
uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear
|
||||
+ DFHack::World::ReadCurrentTick();
|
||||
multimap<uint32_t,EventHandler> newTickQueue;
|
||||
for ( auto i = tickQueue.begin(); i != tickQueue.end(); i++ ) {
|
||||
newTickQueue.insert(pair<uint32_t,EventHandler>(tick + (*i).first, (*i).second));
|
||||
}
|
||||
tickQueue.clear();
|
||||
|
||||
tickQueue.insert(newTickQueue.begin(), newTickQueue.end());
|
||||
|
||||
nextItem = 0;
|
||||
nextBuilding = 0;
|
||||
lastTick = 0;
|
||||
nextInvasion = df::global::ui->invasions.next_id;
|
||||
gameLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
void DFHack::EventManager::manageEvents(color_ostream& out) {
|
||||
if ( !gameLoaded ) {
|
||||
return;
|
||||
}
|
||||
uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear
|
||||
+ DFHack::World::ReadCurrentTick();
|
||||
|
||||
if ( tick <= lastTick )
|
||||
return;
|
||||
lastTick = tick;
|
||||
|
||||
int32_t eventFrequency[EventType::EVENT_MAX];
|
||||
for ( size_t a = 0; a < EventType::EVENT_MAX; a++ ) {
|
||||
int32_t min = 1000000000;
|
||||
for ( auto b = handlers[a].begin(); b != handlers[a].end(); b++ ) {
|
||||
EventHandler bob = (*b).second;
|
||||
if ( bob.freq < min )
|
||||
min = bob.freq;
|
||||
}
|
||||
eventFrequency[a] = min;
|
||||
}
|
||||
|
||||
manageTickEvent(out);
|
||||
if ( tick - eventLastTick[EventType::JOB_INITIATED] >= eventFrequency[EventType::JOB_INITIATED] ) {
|
||||
manageJobInitiatedEvent(out);
|
||||
eventLastTick[EventType::JOB_INITIATED] = tick;
|
||||
}
|
||||
if ( tick - eventLastTick[EventType::JOB_COMPLETED] >= eventFrequency[EventType::JOB_COMPLETED] ) {
|
||||
manageJobCompletedEvent(out);
|
||||
eventLastTick[EventType::JOB_COMPLETED] = tick;
|
||||
}
|
||||
if ( tick - eventLastTick[EventType::UNIT_DEATH] >= eventFrequency[EventType::UNIT_DEATH] ) {
|
||||
manageUnitDeathEvent(out);
|
||||
eventLastTick[EventType::UNIT_DEATH] = tick;
|
||||
}
|
||||
if ( tick - eventLastTick[EventType::ITEM_CREATED] >= eventFrequency[EventType::ITEM_CREATED] ) {
|
||||
manageItemCreationEvent(out);
|
||||
eventLastTick[EventType::ITEM_CREATED] = tick;
|
||||
}
|
||||
if ( tick - eventLastTick[EventType::BUILDING] >= eventFrequency[EventType::BUILDING] ) {
|
||||
manageBuildingEvent(out);
|
||||
eventLastTick[EventType::BUILDING] = tick;
|
||||
}
|
||||
if ( tick - eventLastTick[EventType::CONSTRUCTION] >= eventFrequency[EventType::CONSTRUCTION] ) {
|
||||
manageConstructionEvent(out);
|
||||
eventLastTick[EventType::CONSTRUCTION] = tick;
|
||||
}
|
||||
if ( tick - eventLastTick[EventType::SYNDROME] >= eventFrequency[EventType::SYNDROME] ) {
|
||||
manageSyndromeEvent(out);
|
||||
eventLastTick[EventType::SYNDROME] = tick;
|
||||
}
|
||||
if ( tick - eventLastTick[EventType::INVASION] >= eventFrequency[EventType::INVASION] ) {
|
||||
manageInvasionEvent(out);
|
||||
eventLastTick[EventType::INVASION] = tick;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void manageTickEvent(color_ostream& out) {
|
||||
uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear
|
||||
+ DFHack::World::ReadCurrentTick();
|
||||
while ( !tickQueue.empty() ) {
|
||||
if ( tick < (*tickQueue.begin()).first )
|
||||
break;
|
||||
EventHandler handle = (*tickQueue.begin()).second;
|
||||
tickQueue.erase(tickQueue.begin());
|
||||
handle.eventHandler(out, (void*)tick);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void manageJobInitiatedEvent(color_ostream& out) {
|
||||
if ( handlers[EventType::JOB_INITIATED].empty() )
|
||||
return;
|
||||
|
||||
if ( lastJobId == -1 ) {
|
||||
lastJobId = *df::global::job_next_id - 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if ( lastJobId+1 == *df::global::job_next_id ) {
|
||||
return; //no new jobs
|
||||
}
|
||||
multimap<Plugin*,EventHandler> copy(handlers[EventType::JOB_INITIATED].begin(), handlers[EventType::JOB_INITIATED].end());
|
||||
|
||||
for ( df::job_list_link* link = &df::global::world->job_list; link != NULL; link = link->next ) {
|
||||
if ( link->item == NULL )
|
||||
continue;
|
||||
if ( link->item->id <= lastJobId )
|
||||
continue;
|
||||
for ( auto i = copy.begin(); i != copy.end(); i++ ) {
|
||||
(*i).second.eventHandler(out, (void*)link->item);
|
||||
}
|
||||
}
|
||||
|
||||
lastJobId = *df::global::job_next_id - 1;
|
||||
}
|
||||
|
||||
static void manageJobCompletedEvent(color_ostream& out) {
|
||||
if ( handlers[EventType::JOB_COMPLETED].empty() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
multimap<Plugin*,EventHandler> copy(handlers[EventType::JOB_COMPLETED].begin(), handlers[EventType::JOB_COMPLETED].end());
|
||||
map<int32_t, df::job*> nowJobs;
|
||||
for ( df::job_list_link* link = &df::global::world->job_list; link != NULL; link = link->next ) {
|
||||
if ( link->item == NULL )
|
||||
continue;
|
||||
nowJobs[link->item->id] = link->item;
|
||||
}
|
||||
|
||||
for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) {
|
||||
if ( nowJobs.find((*i).first) != nowJobs.end() )
|
||||
continue;
|
||||
|
||||
//recently finished or cancelled job!
|
||||
for ( auto j = copy.begin(); j != copy.end(); j++ ) {
|
||||
(*j).second.eventHandler(out, (void*)(*i).second);
|
||||
}
|
||||
}
|
||||
|
||||
//erase old jobs, copy over possibly altered jobs
|
||||
for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) {
|
||||
Job::deleteJobStruct((*i).second);
|
||||
}
|
||||
prevJobs.clear();
|
||||
|
||||
//create new jobs
|
||||
for ( auto j = nowJobs.begin(); j != nowJobs.end(); j++ ) {
|
||||
/*map<int32_t, df::job*>::iterator i = prevJobs.find((*j).first);
|
||||
if ( i != prevJobs.end() ) {
|
||||
continue;
|
||||
}*/
|
||||
|
||||
df::job* newJob = Job::cloneJobStruct((*j).second, true);
|
||||
prevJobs[newJob->id] = newJob;
|
||||
}
|
||||
|
||||
/*//get rid of old pointers to deallocated jobs
|
||||
for ( size_t a = 0; a < toDelete.size(); a++ ) {
|
||||
prevJobs.erase(a);
|
||||
}*/
|
||||
}
|
||||
|
||||
static void manageUnitDeathEvent(color_ostream& out) {
|
||||
if ( handlers[EventType::UNIT_DEATH].empty() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
multimap<Plugin*,EventHandler> copy(handlers[EventType::UNIT_DEATH].begin(), handlers[EventType::UNIT_DEATH].end());
|
||||
for ( size_t a = 0; a < df::global::world->units.active.size(); a++ ) {
|
||||
df::unit* unit = df::global::world->units.active[a];
|
||||
if ( unit->counters.death_id == -1 ) {
|
||||
livingUnits.insert(unit->id);
|
||||
continue;
|
||||
}
|
||||
//dead: if dead since last check, trigger events
|
||||
if ( livingUnits.find(unit->id) == livingUnits.end() )
|
||||
continue;
|
||||
|
||||
for ( auto i = copy.begin(); i != copy.end(); i++ ) {
|
||||
(*i).second.eventHandler(out, (void*)unit->id);
|
||||
}
|
||||
livingUnits.erase(unit->id);
|
||||
}
|
||||
}
|
||||
|
||||
static void manageItemCreationEvent(color_ostream& out) {
|
||||
if ( handlers[EventType::ITEM_CREATED].empty() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( nextItem >= *df::global::item_next_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
multimap<Plugin*,EventHandler> copy(handlers[EventType::ITEM_CREATED].begin(), handlers[EventType::ITEM_CREATED].end());
|
||||
size_t index = df::item::binsearch_index(df::global::world->items.all, nextItem, false);
|
||||
for ( size_t a = index; a < df::global::world->items.all.size(); a++ ) {
|
||||
df::item* item = df::global::world->items.all[a];
|
||||
//invaders
|
||||
if ( item->flags.bits.foreign )
|
||||
continue;
|
||||
//traders who bring back your items?
|
||||
if ( item->flags.bits.trader )
|
||||
continue;
|
||||
//migrants
|
||||
if ( item->flags.bits.owned )
|
||||
continue;
|
||||
//spider webs don't count
|
||||
if ( item->flags.bits.spider_web )
|
||||
continue;
|
||||
for ( auto i = copy.begin(); i != copy.end(); i++ ) {
|
||||
(*i).second.eventHandler(out, (void*)item->id);
|
||||
}
|
||||
}
|
||||
nextItem = *df::global::item_next_id;
|
||||
}
|
||||
|
||||
static void manageBuildingEvent(color_ostream& out) {
|
||||
/*
|
||||
* TODO: could be faster
|
||||
* consider looking at jobs: building creation / destruction
|
||||
**/
|
||||
if ( handlers[EventType::BUILDING].empty() )
|
||||
return;
|
||||
|
||||
multimap<Plugin*,EventHandler> copy(handlers[EventType::BUILDING].begin(), handlers[EventType::BUILDING].end());
|
||||
//first alert people about new buildings
|
||||
for ( int32_t a = nextBuilding; a < *df::global::building_next_id; a++ ) {
|
||||
int32_t index = df::building::binsearch_index(df::global::world->buildings.all, a);
|
||||
if ( index == -1 ) {
|
||||
//out.print("%s, line %d: Couldn't find new building with id %d.\n", __FILE__, __LINE__, a);
|
||||
//the tricky thing is that when the game first starts, it's ok to skip buildings, but otherwise, if you skip buildings, something is probably wrong. TODO: make this smarter
|
||||
continue;
|
||||
}
|
||||
buildings.insert(a);
|
||||
for ( auto b = copy.begin(); b != copy.end(); b++ ) {
|
||||
EventHandler bob = (*b).second;
|
||||
bob.eventHandler(out, (void*)a);
|
||||
}
|
||||
}
|
||||
nextBuilding = *df::global::building_next_id;
|
||||
|
||||
//now alert people about destroyed buildings
|
||||
unordered_set<int32_t> toDelete;
|
||||
for ( auto a = buildings.begin(); a != buildings.end(); a++ ) {
|
||||
int32_t id = *a;
|
||||
int32_t index = df::building::binsearch_index(df::global::world->buildings.all,id);
|
||||
if ( index != -1 )
|
||||
continue;
|
||||
toDelete.insert(id);
|
||||
|
||||
for ( auto b = copy.begin(); b != copy.end(); b++ ) {
|
||||
EventHandler bob = (*b).second;
|
||||
bob.eventHandler(out, (void*)id);
|
||||
}
|
||||
}
|
||||
|
||||
for ( auto a = toDelete.begin(); a != toDelete.end(); a++ ) {
|
||||
int32_t id = *a;
|
||||
buildings.erase(id);
|
||||
}
|
||||
|
||||
//out.print("Sent building event.\n %d", __LINE__);
|
||||
}
|
||||
|
||||
static void manageConstructionEvent(color_ostream& out) {
|
||||
if ( handlers[EventType::CONSTRUCTION].empty() )
|
||||
return;
|
||||
|
||||
unordered_set<df::construction*> constructionsNow(df::global::world->constructions.begin(), df::global::world->constructions.end());
|
||||
|
||||
multimap<Plugin*,EventHandler> copy(handlers[EventType::CONSTRUCTION].begin(), handlers[EventType::CONSTRUCTION].end());
|
||||
for ( auto a = constructions.begin(); a != constructions.end(); a++ ) {
|
||||
df::construction* construction = *a;
|
||||
if ( constructionsNow.find(construction) != constructionsNow.end() )
|
||||
continue;
|
||||
for ( auto b = copy.begin(); b != copy.end(); b++ ) {
|
||||
EventHandler handle = (*b).second;
|
||||
handle.eventHandler(out, (void*)construction);
|
||||
}
|
||||
}
|
||||
|
||||
for ( auto a = constructionsNow.begin(); a != constructionsNow.end(); a++ ) {
|
||||
df::construction* construction = *a;
|
||||
if ( constructions.find(construction) != constructions.end() )
|
||||
continue;
|
||||
for ( auto b = copy.begin(); b != copy.end(); b++ ) {
|
||||
EventHandler handle = (*b).second;
|
||||
handle.eventHandler(out, (void*)construction);
|
||||
}
|
||||
}
|
||||
|
||||
constructions.clear();
|
||||
constructions.insert(constructionsNow.begin(), constructionsNow.end());
|
||||
}
|
||||
|
||||
static void manageSyndromeEvent(color_ostream& out) {
|
||||
if ( handlers[EventType::SYNDROME].empty() )
|
||||
return;
|
||||
|
||||
multimap<Plugin*,EventHandler> copy(handlers[EventType::SYNDROME].begin(), handlers[EventType::SYNDROME].end());
|
||||
for ( auto a = df::global::world->units.active.begin(); a != df::global::world->units.active.end(); a++ ) {
|
||||
df::unit* unit = *a;
|
||||
if ( unit->flags1.bits.dead )
|
||||
continue;
|
||||
for ( size_t b = 0; b < unit->syndromes.active.size(); b++ ) {
|
||||
df::unit_syndrome* syndrome = unit->syndromes.active[b];
|
||||
uint32_t startTime = syndrome->year*ticksPerYear + syndrome->year_time;
|
||||
if ( startTime <= eventLastTick[EventType::SYNDROME] )
|
||||
continue;
|
||||
|
||||
SyndromeData data(unit->id, b);
|
||||
for ( auto c = copy.begin(); c != copy.end(); c++ ) {
|
||||
EventHandler handle = (*c).second;
|
||||
handle.eventHandler(out, (void*)&data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void manageInvasionEvent(color_ostream& out) {
|
||||
if ( handlers[EventType::INVASION].empty() )
|
||||
return;
|
||||
|
||||
multimap<Plugin*,EventHandler> copy(handlers[EventType::INVASION].begin(), handlers[EventType::INVASION].end());
|
||||
|
||||
if ( df::global::ui->invasions.next_id <= nextInvasion )
|
||||
return;
|
||||
nextInvasion = df::global::ui->invasions.next_id;
|
||||
|
||||
for ( auto a = copy.begin(); a != copy.end(); a++ ) {
|
||||
EventHandler handle = (*a).second;
|
||||
handle.eventHandler(out, (void*)nextInvasion);
|
||||
}
|
||||
}
|
||||
|