Merge branch 'master' of https://github.com/angavrilov/dfhack
commit
93662034fe
@ -1 +1 @@
|
|||||||
Subproject commit 49fa800615a4e5c872164bcb4122030d2ebda9cf
|
Subproject commit c85e9fb35d3510c5dcc367056cda3237d77a7add
|
@ -0,0 +1,773 @@
|
|||||||
|
/*
|
||||||
|
https://github.com/peterix/dfhack
|
||||||
|
|
||||||
|
A thread-safe logging console with a line editor.
|
||||||
|
|
||||||
|
Based on linenoise:
|
||||||
|
linenoise -- guerrilla line editing library against the idea that a
|
||||||
|
line editing lib needs to be 20,000 lines of C code.
|
||||||
|
|
||||||
|
You can find the latest source code at:
|
||||||
|
|
||||||
|
http://github.com/antirez/linenoise
|
||||||
|
|
||||||
|
Does a number of crazy assumptions that happen to be true in 99.9999% of
|
||||||
|
the 2010 UNIX computers around.
|
||||||
|
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||||
|
Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
|
||||||
|
Copyright (c) 2011, Petr Mrázek <peterix@gmail.com>
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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
|
||||||
|
HOLDER 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 <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string.h>
|
||||||
|
#include <string>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <deque>
|
||||||
|
|
||||||
|
// George Vulov for MacOSX
|
||||||
|
#ifndef __LINUX__
|
||||||
|
#define TEMP_FAILURE_RETRY(expr) \
|
||||||
|
({ long int _res; \
|
||||||
|
do _res = (long int) (expr); \
|
||||||
|
while (_res == -1L && errno == EINTR); \
|
||||||
|
_res; })
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "Console.h"
|
||||||
|
#include "Hooks.h"
|
||||||
|
using namespace DFHack;
|
||||||
|
|
||||||
|
#include "tinythread.h"
|
||||||
|
using namespace tthread;
|
||||||
|
|
||||||
|
static int isUnsupportedTerm(void)
|
||||||
|
{
|
||||||
|
static const char *unsupported_term[] = {"dumb","cons25",NULL};
|
||||||
|
char *term = getenv("TERM");
|
||||||
|
int j;
|
||||||
|
|
||||||
|
if (term == NULL) return 0;
|
||||||
|
for (j = 0; unsupported_term[j]; j++)
|
||||||
|
if (!strcasecmp(term,unsupported_term[j])) return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char * ANSI_CLS = "\033[2J";
|
||||||
|
const char * ANSI_BLACK = "\033[22;30m";
|
||||||
|
const char * ANSI_RED = "\033[22;31m";
|
||||||
|
const char * ANSI_GREEN = "\033[22;32m";
|
||||||
|
const char * ANSI_BROWN = "\033[22;33m";
|
||||||
|
const char * ANSI_BLUE = "\033[22;34m";
|
||||||
|
const char * ANSI_MAGENTA = "\033[22;35m";
|
||||||
|
const char * ANSI_CYAN = "\033[22;36m";
|
||||||
|
const char * ANSI_GREY = "\033[22;37m";
|
||||||
|
const char * ANSI_DARKGREY = "\033[01;30m";
|
||||||
|
const char * ANSI_LIGHTRED = "\033[01;31m";
|
||||||
|
const char * ANSI_LIGHTGREEN = "\033[01;32m";
|
||||||
|
const char * ANSI_YELLOW = "\033[01;33m";
|
||||||
|
const char * ANSI_LIGHTBLUE = "\033[01;34m";
|
||||||
|
const char * ANSI_LIGHTMAGENTA = "\033[01;35m";
|
||||||
|
const char * ANSI_LIGHTCYAN = "\033[01;36m";
|
||||||
|
const char * ANSI_WHITE = "\033[01;37m";
|
||||||
|
const char * RESETCOLOR = "\033[0m";
|
||||||
|
|
||||||
|
const char * getANSIColor(const int c)
|
||||||
|
{
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case -1: return RESETCOLOR; // HACK! :P
|
||||||
|
case 0 : return ANSI_BLACK;
|
||||||
|
case 1 : return ANSI_BLUE; // non-ANSI
|
||||||
|
case 2 : return ANSI_GREEN;
|
||||||
|
case 3 : return ANSI_CYAN; // non-ANSI
|
||||||
|
case 4 : return ANSI_RED; // non-ANSI
|
||||||
|
case 5 : return ANSI_MAGENTA;
|
||||||
|
case 6 : return ANSI_BROWN;
|
||||||
|
case 7 : return ANSI_GREY;
|
||||||
|
case 8 : return ANSI_DARKGREY;
|
||||||
|
case 9 : return ANSI_LIGHTBLUE; // non-ANSI
|
||||||
|
case 10: return ANSI_LIGHTGREEN;
|
||||||
|
case 11: return ANSI_LIGHTCYAN; // non-ANSI;
|
||||||
|
case 12: return ANSI_LIGHTRED; // non-ANSI;
|
||||||
|
case 13: return ANSI_LIGHTMAGENTA;
|
||||||
|
case 14: return ANSI_YELLOW; // non-ANSI
|
||||||
|
case 15: return ANSI_WHITE;
|
||||||
|
default: return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace DFHack
|
||||||
|
{
|
||||||
|
class Private
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Private()
|
||||||
|
{
|
||||||
|
dfout_C = NULL;
|
||||||
|
rawmode = false;
|
||||||
|
in_batch = false;
|
||||||
|
supported_terminal = false;
|
||||||
|
state = con_unclaimed;
|
||||||
|
};
|
||||||
|
virtual ~Private()
|
||||||
|
{
|
||||||
|
//sync();
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
bool read_char(unsigned char & out)
|
||||||
|
{
|
||||||
|
FD_ZERO(&descriptor_set);
|
||||||
|
FD_SET(STDIN_FILENO, &descriptor_set);
|
||||||
|
FD_SET(exit_pipe[0], &descriptor_set);
|
||||||
|
int ret = TEMP_FAILURE_RETRY(
|
||||||
|
select (FD_SETSIZE,&descriptor_set, NULL, NULL, NULL)
|
||||||
|
);
|
||||||
|
if(ret == -1)
|
||||||
|
return false;
|
||||||
|
if (FD_ISSET(exit_pipe[0], &descriptor_set))
|
||||||
|
return false;
|
||||||
|
if (FD_ISSET(STDIN_FILENO, &descriptor_set))
|
||||||
|
{
|
||||||
|
// read byte from stdin
|
||||||
|
ret = TEMP_FAILURE_RETRY(
|
||||||
|
read(STDIN_FILENO, &out, 1)
|
||||||
|
);
|
||||||
|
if(ret == -1)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
void print(const char *data)
|
||||||
|
{
|
||||||
|
fputs(data, dfout_C);
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_text(color_ostream::color_value clr, const std::string &chunk)
|
||||||
|
{
|
||||||
|
if(!in_batch && state == con_lineedit)
|
||||||
|
{
|
||||||
|
disable_raw();
|
||||||
|
fprintf(dfout_C,"\x1b[1G");
|
||||||
|
fprintf(dfout_C,"\x1b[0K");
|
||||||
|
|
||||||
|
color(clr);
|
||||||
|
print(chunk.c_str());
|
||||||
|
|
||||||
|
reset_color();
|
||||||
|
enable_raw();
|
||||||
|
prompt_refresh();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
color(clr);
|
||||||
|
print(chunk.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void begin_batch()
|
||||||
|
{
|
||||||
|
assert(!in_batch);
|
||||||
|
|
||||||
|
in_batch = true;
|
||||||
|
|
||||||
|
if (state == con_lineedit)
|
||||||
|
{
|
||||||
|
disable_raw();
|
||||||
|
fprintf(dfout_C,"\x1b[1G");
|
||||||
|
fprintf(dfout_C,"\x1b[0K");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void end_batch()
|
||||||
|
{
|
||||||
|
assert(in_batch);
|
||||||
|
|
||||||
|
flush();
|
||||||
|
|
||||||
|
in_batch = false;
|
||||||
|
|
||||||
|
if (state == con_lineedit)
|
||||||
|
{
|
||||||
|
reset_color();
|
||||||
|
enable_raw();
|
||||||
|
prompt_refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void flush()
|
||||||
|
{
|
||||||
|
if (!rawmode)
|
||||||
|
fflush(dfout_C);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear the console, along with its scrollback
|
||||||
|
void clear()
|
||||||
|
{
|
||||||
|
if(rawmode)
|
||||||
|
{
|
||||||
|
const char * clr = "\033c\033[3J\033[H";
|
||||||
|
::write(STDIN_FILENO,clr,strlen(clr));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
print("\033c\033[3J\033[H");
|
||||||
|
fflush(dfout_C);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Position cursor at x,y. 1,1 = top left corner
|
||||||
|
void gotoxy(int x, int y)
|
||||||
|
{
|
||||||
|
char tmp[64];
|
||||||
|
sprintf(tmp,"\033[%d;%dH", y,x);
|
||||||
|
print(tmp);
|
||||||
|
}
|
||||||
|
/// Set color (ANSI color number)
|
||||||
|
void color(Console::color_value index)
|
||||||
|
{
|
||||||
|
if(!rawmode)
|
||||||
|
fprintf(dfout_C,getANSIColor(index));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const char * colstr = getANSIColor(index);
|
||||||
|
int lstr = strlen(colstr);
|
||||||
|
::write(STDIN_FILENO,colstr,lstr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Reset color to default
|
||||||
|
void reset_color(void)
|
||||||
|
{
|
||||||
|
color(Console::COLOR_RESET);
|
||||||
|
if(!rawmode)
|
||||||
|
fflush(dfout_C);
|
||||||
|
}
|
||||||
|
/// Enable or disable the caret/cursor
|
||||||
|
void cursor(bool enable = true)
|
||||||
|
{
|
||||||
|
if(enable)
|
||||||
|
print("\033[?25h");
|
||||||
|
else
|
||||||
|
print("\033[?25l");
|
||||||
|
}
|
||||||
|
/// Waits given number of milliseconds before continuing.
|
||||||
|
void msleep(unsigned int msec);
|
||||||
|
/// get the current number of columns
|
||||||
|
int get_columns(void)
|
||||||
|
{
|
||||||
|
winsize ws;
|
||||||
|
if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1) return 80;
|
||||||
|
return ws.ws_col;
|
||||||
|
}
|
||||||
|
/// get the current number of rows
|
||||||
|
int get_rows(void)
|
||||||
|
{
|
||||||
|
winsize ws;
|
||||||
|
if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1) return 25;
|
||||||
|
return ws.ws_row;
|
||||||
|
}
|
||||||
|
/// beep. maybe?
|
||||||
|
//void beep (void);
|
||||||
|
/// A simple line edit (raw mode)
|
||||||
|
int lineedit(const std::string& prompt, std::string& output, recursive_mutex * lock, CommandHistory & ch)
|
||||||
|
{
|
||||||
|
output.clear();
|
||||||
|
reset_color();
|
||||||
|
this->prompt = prompt;
|
||||||
|
if (!supported_terminal)
|
||||||
|
{
|
||||||
|
print(prompt.c_str());
|
||||||
|
fflush(dfout_C);
|
||||||
|
// FIXME: what do we do here???
|
||||||
|
//SDL_recursive_mutexV(lock);
|
||||||
|
std::getline(std::cin, output);
|
||||||
|
//SDL_recursive_mutexP(lock);
|
||||||
|
return output.size();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int count;
|
||||||
|
if (enable_raw() == -1) return 0;
|
||||||
|
if(state == con_lineedit)
|
||||||
|
return -1;
|
||||||
|
state = con_lineedit;
|
||||||
|
count = prompt_loop(lock,ch);
|
||||||
|
state = con_unclaimed;
|
||||||
|
disable_raw();
|
||||||
|
print("\n");
|
||||||
|
if(count != -1)
|
||||||
|
{
|
||||||
|
output = raw_buffer;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int enable_raw()
|
||||||
|
{
|
||||||
|
struct termios raw;
|
||||||
|
|
||||||
|
if (!supported_terminal)
|
||||||
|
return -1;
|
||||||
|
if (tcgetattr(STDIN_FILENO,&orig_termios) == -1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
raw = orig_termios; //modify the original mode
|
||||||
|
// input modes: no break, no CR to NL, no parity check, no strip char,
|
||||||
|
// no start/stop output control.
|
||||||
|
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
|
||||||
|
// output modes - disable post processing
|
||||||
|
raw.c_oflag &= ~(OPOST);
|
||||||
|
// control modes - set 8 bit chars
|
||||||
|
raw.c_cflag |= (CS8);
|
||||||
|
// local modes - choing off, canonical off, no extended functions,
|
||||||
|
// no signal chars (^Z,^C)
|
||||||
|
#ifdef CONSOLE_NO_CATCH
|
||||||
|
raw.c_lflag &= ~( ECHO | ICANON | IEXTEN );
|
||||||
|
#else
|
||||||
|
raw.c_lflag &= ~( ECHO | ICANON | IEXTEN | ISIG );
|
||||||
|
#endif
|
||||||
|
// control chars - set return condition: min number of bytes and timer.
|
||||||
|
// We want read to return every single byte, without timeout.
|
||||||
|
raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0;// 1 byte, no timer
|
||||||
|
// put terminal in raw mode after flushing
|
||||||
|
if (tcsetattr(STDIN_FILENO,TCSAFLUSH,&raw) < 0)
|
||||||
|
return -1;
|
||||||
|
rawmode = 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void disable_raw()
|
||||||
|
{
|
||||||
|
/* Don't even check the return value as it's too late. */
|
||||||
|
if (rawmode && tcsetattr(STDIN_FILENO,TCSAFLUSH,&orig_termios) != -1)
|
||||||
|
rawmode = 0;
|
||||||
|
}
|
||||||
|
void prompt_refresh()
|
||||||
|
{
|
||||||
|
char seq[64];
|
||||||
|
int cols = get_columns();
|
||||||
|
int plen = prompt.size();
|
||||||
|
const char * buf = raw_buffer.c_str();
|
||||||
|
int len = raw_buffer.size();
|
||||||
|
int cooked_cursor = raw_cursor;
|
||||||
|
// Use math! This is silly.
|
||||||
|
while((plen+cooked_cursor) >= cols)
|
||||||
|
{
|
||||||
|
buf++;
|
||||||
|
len--;
|
||||||
|
cooked_cursor--;
|
||||||
|
}
|
||||||
|
while (plen+len > cols)
|
||||||
|
{
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
/* Cursor to left edge */
|
||||||
|
snprintf(seq,64,"\x1b[1G");
|
||||||
|
if (::write(STDIN_FILENO,seq,strlen(seq)) == -1) return;
|
||||||
|
/* Write the prompt and the current buffer content */
|
||||||
|
if (::write(STDIN_FILENO,prompt.c_str(),plen) == -1) return;
|
||||||
|
if (::write(STDIN_FILENO,buf,len) == -1) return;
|
||||||
|
/* Erase to right */
|
||||||
|
snprintf(seq,64,"\x1b[0K");
|
||||||
|
if (::write(STDIN_FILENO,seq,strlen(seq)) == -1) return;
|
||||||
|
/* Move cursor to original position. */
|
||||||
|
snprintf(seq,64,"\x1b[1G\x1b[%dC", (int)(cooked_cursor+plen));
|
||||||
|
if (::write(STDIN_FILENO,seq,strlen(seq)) == -1) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int prompt_loop(recursive_mutex * lock, CommandHistory & history)
|
||||||
|
{
|
||||||
|
int fd = STDIN_FILENO;
|
||||||
|
size_t plen = prompt.size();
|
||||||
|
int history_index = 0;
|
||||||
|
raw_buffer.clear();
|
||||||
|
raw_cursor = 0;
|
||||||
|
/* The latest history entry is always our current buffer, that
|
||||||
|
* initially is just an empty string. */
|
||||||
|
const std::string empty;
|
||||||
|
history.add(empty);
|
||||||
|
if (::write(fd,prompt.c_str(),prompt.size()) == -1) return -1;
|
||||||
|
while(1)
|
||||||
|
{
|
||||||
|
unsigned char c;
|
||||||
|
int isok;
|
||||||
|
unsigned char seq[2], seq2;
|
||||||
|
lock->unlock();
|
||||||
|
if(!read_char(c))
|
||||||
|
{
|
||||||
|
lock->lock();
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
lock->lock();
|
||||||
|
/* Only autocomplete when the callback is set. It returns < 0 when
|
||||||
|
* there was an error reading from fd. Otherwise it will return the
|
||||||
|
* character that should be handled next. */
|
||||||
|
if (c == 9)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
if( completionCallback != NULL) {
|
||||||
|
c = completeLine(fd,prompt,buf,buflen,&len,&pos,cols);
|
||||||
|
// Return on errors
|
||||||
|
if (c < 0) return len;
|
||||||
|
// Read next character when 0
|
||||||
|
if (c == 0) continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// ignore tab
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
// just ignore tabs
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(c)
|
||||||
|
{
|
||||||
|
case 13: // enter
|
||||||
|
history.remove();
|
||||||
|
return raw_buffer.size();
|
||||||
|
case 3: // ctrl-c
|
||||||
|
errno = EAGAIN;
|
||||||
|
return -1;
|
||||||
|
case 127: // backspace
|
||||||
|
case 8: // ctrl-h
|
||||||
|
if (raw_cursor > 0 && raw_buffer.size() > 0)
|
||||||
|
{
|
||||||
|
raw_buffer.erase(raw_cursor-1,1);
|
||||||
|
raw_cursor--;
|
||||||
|
prompt_refresh();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 27: // escape sequence
|
||||||
|
lock->unlock();
|
||||||
|
if(!read_char(seq[0]) || !read_char(seq[1]))
|
||||||
|
{
|
||||||
|
lock->lock();
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
lock->lock();
|
||||||
|
if(seq[0] == '[')
|
||||||
|
{
|
||||||
|
if (seq[1] == 'D')
|
||||||
|
{
|
||||||
|
left_arrow:
|
||||||
|
if (raw_cursor > 0)
|
||||||
|
{
|
||||||
|
raw_cursor--;
|
||||||
|
prompt_refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ( seq[1] == 'C')
|
||||||
|
{
|
||||||
|
right_arrow:
|
||||||
|
/* right arrow */
|
||||||
|
if (size_t(raw_cursor) != raw_buffer.size())
|
||||||
|
{
|
||||||
|
raw_cursor++;
|
||||||
|
prompt_refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (seq[1] == 'A' || seq[1] == 'B')
|
||||||
|
{
|
||||||
|
/* up and down arrow: history */
|
||||||
|
if (history.size() > 1)
|
||||||
|
{
|
||||||
|
/* Update the current history entry before to
|
||||||
|
* overwrite it with tne next one. */
|
||||||
|
history[history_index] = raw_buffer;
|
||||||
|
/* Show the new entry */
|
||||||
|
history_index += (seq[1] == 'A') ? 1 : -1;
|
||||||
|
if (history_index < 0)
|
||||||
|
{
|
||||||
|
history_index = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (size_t(history_index) >= history.size())
|
||||||
|
{
|
||||||
|
history_index = history.size()-1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
raw_buffer = history[history_index];
|
||||||
|
raw_cursor = raw_buffer.size();
|
||||||
|
prompt_refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(seq[1] == 'H')
|
||||||
|
{
|
||||||
|
// home
|
||||||
|
raw_cursor = 0;
|
||||||
|
prompt_refresh();
|
||||||
|
}
|
||||||
|
else if(seq[1] == 'F')
|
||||||
|
{
|
||||||
|
// end
|
||||||
|
raw_cursor = raw_buffer.size();
|
||||||
|
prompt_refresh();
|
||||||
|
}
|
||||||
|
else if (seq[1] > '0' && seq[1] < '7')
|
||||||
|
{
|
||||||
|
// extended escape
|
||||||
|
lock->unlock();
|
||||||
|
if(!read_char(seq2))
|
||||||
|
{
|
||||||
|
lock->lock();
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
lock->lock();
|
||||||
|
if (seq[1] == '3' && seq2 == '~' )
|
||||||
|
{
|
||||||
|
// delete
|
||||||
|
if (raw_buffer.size() > 0 && size_t(raw_cursor) < raw_buffer.size())
|
||||||
|
{
|
||||||
|
raw_buffer.erase(raw_cursor,1);
|
||||||
|
prompt_refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (raw_buffer.size() == size_t(raw_cursor))
|
||||||
|
{
|
||||||
|
raw_buffer.append(1,c);
|
||||||
|
raw_cursor++;
|
||||||
|
if (plen+raw_buffer.size() < size_t(get_columns()))
|
||||||
|
{
|
||||||
|
/* Avoid a full update of the line in the
|
||||||
|
* trivial case. */
|
||||||
|
if (::write(fd,&c,1) == -1) return -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
prompt_refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
raw_buffer.insert(raw_cursor,1,c);
|
||||||
|
raw_cursor++;
|
||||||
|
prompt_refresh();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 21: // Ctrl+u, delete the whole line.
|
||||||
|
raw_buffer.clear();
|
||||||
|
raw_cursor = 0;
|
||||||
|
prompt_refresh();
|
||||||
|
break;
|
||||||
|
case 11: // Ctrl+k, delete from current to end of line.
|
||||||
|
raw_buffer.erase(raw_cursor);
|
||||||
|
prompt_refresh();
|
||||||
|
break;
|
||||||
|
case 1: // Ctrl+a, go to the start of the line
|
||||||
|
raw_cursor = 0;
|
||||||
|
prompt_refresh();
|
||||||
|
break;
|
||||||
|
case 5: // ctrl+e, go to the end of the line
|
||||||
|
raw_cursor = raw_buffer.size();
|
||||||
|
prompt_refresh();
|
||||||
|
break;
|
||||||
|
case 12: // ctrl+l, clear screen
|
||||||
|
clear();
|
||||||
|
prompt_refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return raw_buffer.size();
|
||||||
|
}
|
||||||
|
FILE * dfout_C;
|
||||||
|
bool supported_terminal;
|
||||||
|
// state variables
|
||||||
|
bool rawmode; // is raw mode active?
|
||||||
|
termios orig_termios; // saved/restored by raw mode
|
||||||
|
// current state
|
||||||
|
enum console_state
|
||||||
|
{
|
||||||
|
con_unclaimed,
|
||||||
|
con_lineedit
|
||||||
|
} state;
|
||||||
|
bool in_batch;
|
||||||
|
std::string prompt; // current prompt string
|
||||||
|
std::string raw_buffer; // current raw mode buffer
|
||||||
|
int raw_cursor; // cursor position in the buffer
|
||||||
|
// thread exit mechanism
|
||||||
|
int exit_pipe[2];
|
||||||
|
fd_set descriptor_set;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Console::Console()
|
||||||
|
{
|
||||||
|
d = 0;
|
||||||
|
inited = false;
|
||||||
|
// we can't create the mutex at this time. the SDL functions aren't hooked yet.
|
||||||
|
wlock = new recursive_mutex();
|
||||||
|
}
|
||||||
|
Console::~Console()
|
||||||
|
{
|
||||||
|
if(inited)
|
||||||
|
shutdown();
|
||||||
|
if(wlock)
|
||||||
|
delete wlock;
|
||||||
|
if(d)
|
||||||
|
delete d;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Console::init(bool sharing)
|
||||||
|
{
|
||||||
|
if(sharing)
|
||||||
|
{
|
||||||
|
inited = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
freopen("stdout.log", "w", stdout);
|
||||||
|
d = new Private();
|
||||||
|
// make our own weird streams so our IO isn't redirected
|
||||||
|
d->dfout_C = fopen("/dev/tty", "w");
|
||||||
|
std::cin.tie(this);
|
||||||
|
clear();
|
||||||
|
d->supported_terminal = !isUnsupportedTerm() && isatty(STDIN_FILENO);
|
||||||
|
// init the exit mechanism
|
||||||
|
pipe(d->exit_pipe);
|
||||||
|
FD_ZERO(&d->descriptor_set);
|
||||||
|
FD_SET(STDIN_FILENO, &d->descriptor_set);
|
||||||
|
FD_SET(d->exit_pipe[0], &d->descriptor_set);
|
||||||
|
inited = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Console::shutdown(void)
|
||||||
|
{
|
||||||
|
if(!d)
|
||||||
|
return true;
|
||||||
|
lock_guard <recursive_mutex> g(*wlock);
|
||||||
|
if(d->rawmode)
|
||||||
|
d->disable_raw();
|
||||||
|
d->print("\n");
|
||||||
|
inited = false;
|
||||||
|
// kill the thing
|
||||||
|
close(d->exit_pipe[1]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Console::begin_batch()
|
||||||
|
{
|
||||||
|
//color_ostream::begin_batch();
|
||||||
|
|
||||||
|
wlock->lock();
|
||||||
|
|
||||||
|
if (inited)
|
||||||
|
d->begin_batch();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Console::end_batch()
|
||||||
|
{
|
||||||
|
if (inited)
|
||||||
|
d->end_batch();
|
||||||
|
|
||||||
|
wlock->unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Console::flush_proxy()
|
||||||
|
{
|
||||||
|
lock_guard <recursive_mutex> g(*wlock);
|
||||||
|
if (inited)
|
||||||
|
d->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Console::add_text(color_value color, const std::string &text)
|
||||||
|
{
|
||||||
|
lock_guard <recursive_mutex> g(*wlock);
|
||||||
|
if (inited)
|
||||||
|
d->print_text(color, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Console::get_columns(void)
|
||||||
|
{
|
||||||
|
lock_guard <recursive_mutex> g(*wlock);
|
||||||
|
int ret = -1;
|
||||||
|
if(inited)
|
||||||
|
ret = d->get_columns();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Console::get_rows(void)
|
||||||
|
{
|
||||||
|
lock_guard <recursive_mutex> g(*wlock);
|
||||||
|
int ret = -1;
|
||||||
|
if(inited)
|
||||||
|
ret = d->get_rows();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Console::clear()
|
||||||
|
{
|
||||||
|
lock_guard <recursive_mutex> g(*wlock);
|
||||||
|
if(inited)
|
||||||
|
d->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Console::gotoxy(int x, int y)
|
||||||
|
{
|
||||||
|
lock_guard <recursive_mutex> g(*wlock);
|
||||||
|
if(inited)
|
||||||
|
d->gotoxy(x,y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Console::cursor(bool enable)
|
||||||
|
{
|
||||||
|
lock_guard <recursive_mutex> g(*wlock);
|
||||||
|
if(inited)
|
||||||
|
d->cursor(enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Console::lineedit(const std::string & prompt, std::string & output, CommandHistory & ch)
|
||||||
|
{
|
||||||
|
lock_guard <recursive_mutex> g(*wlock);
|
||||||
|
int ret = -2;
|
||||||
|
if(inited)
|
||||||
|
ret = d->lineedit(prompt,output,wlock,ch);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Console::msleep (unsigned int msec)
|
||||||
|
{
|
||||||
|
if (msec > 1000) sleep(msec/1000000);
|
||||||
|
usleep((msec % 1000000) * 1000);
|
||||||
|
}
|
@ -0,0 +1,163 @@
|
|||||||
|
/*
|
||||||
|
https://github.com/peterix/dfhack
|
||||||
|
Copyright (c) 2009-2011 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <dlfcn.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/shm.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/ipc.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
/*typedef struct interpose_s
|
||||||
|
{
|
||||||
|
void *new_func;
|
||||||
|
void *orig_func;
|
||||||
|
} interpose_t;*/
|
||||||
|
|
||||||
|
#include "DFHack.h"
|
||||||
|
#include "Core.h"
|
||||||
|
#include "Hooks.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
/*static const interpose_t interposers[] __attribute__ ((section("__DATA, __interpose"))) =
|
||||||
|
{
|
||||||
|
{ (void *)DFH_SDL_Init, (void *)SDL_Init },
|
||||||
|
{ (void *)DFH_SDL_PollEvent, (void *)SDL_PollEvent },
|
||||||
|
{ (void *)DFH_SDL_Quit, (void *)SDL_Quit },
|
||||||
|
{ (void *)DFH_SDL_NumJoysticks, (void *)SDL_NumJoysticks },
|
||||||
|
|
||||||
|
};*/
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* SDL part starts here *
|
||||||
|
*******************************************************************************/
|
||||||
|
// hook - called for each game tick (or more often)
|
||||||
|
DFhackCExport int SDL_NumJoysticks(void)
|
||||||
|
{
|
||||||
|
DFHack::Core & c = DFHack::Core::getInstance();
|
||||||
|
return c.Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
// hook - called at program exit
|
||||||
|
static void (*_SDL_Quit)(void) = 0;
|
||||||
|
DFhackCExport void SDL_Quit(void)
|
||||||
|
{
|
||||||
|
DFHack::Core & c = DFHack::Core::getInstance();
|
||||||
|
c.Shutdown();
|
||||||
|
/*if(_SDL_Quit)
|
||||||
|
{
|
||||||
|
_SDL_Quit();
|
||||||
|
}*/
|
||||||
|
|
||||||
|
_SDL_Quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// called by DF to check input events
|
||||||
|
static int (*_SDL_PollEvent)(SDL_Event* event) = 0;
|
||||||
|
DFhackCExport int SDL_PollEvent(SDL_Event* event)
|
||||||
|
{
|
||||||
|
pollevent_again:
|
||||||
|
// if SDL returns 0 here, it means there are no more events. return 0
|
||||||
|
int orig_return = _SDL_PollEvent(event);
|
||||||
|
if(!orig_return)
|
||||||
|
return 0;
|
||||||
|
// otherwise we have an event to filter
|
||||||
|
else if( event != 0 )
|
||||||
|
{
|
||||||
|
DFHack::Core & c = DFHack::Core::getInstance();
|
||||||
|
// if we consume the event, ask SDL for more.
|
||||||
|
if(!c.DFH_SDL_Event(event))
|
||||||
|
goto pollevent_again;
|
||||||
|
}
|
||||||
|
return orig_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WINDOW;
|
||||||
|
DFhackCExport int wgetch(WINDOW *win)
|
||||||
|
{
|
||||||
|
static int (*_wgetch)(WINDOW * win) = (int (*)( WINDOW * )) dlsym(RTLD_NEXT, "wgetch");
|
||||||
|
if(!_wgetch)
|
||||||
|
{
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
DFHack::Core & c = DFHack::Core::getInstance();
|
||||||
|
wgetch_again:
|
||||||
|
int in = _wgetch(win);
|
||||||
|
int out;
|
||||||
|
if(c.ncurses_wgetch(in, out))
|
||||||
|
{
|
||||||
|
// not consumed, give to DF
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// consumed, repeat
|
||||||
|
goto wgetch_again;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// hook - called at program start, initialize some stuffs we'll use later
|
||||||
|
static int (*_SDL_Init)(uint32_t flags) = 0;
|
||||||
|
DFhackCExport int SDL_Init(uint32_t flags)
|
||||||
|
{
|
||||||
|
// reroute stderr
|
||||||
|
fprintf(stderr,"dfhack: attempting to hook in\n");
|
||||||
|
freopen("stderr.log", "w", stderr);
|
||||||
|
// we don't reroute stdout until we figure out if this should be done at all
|
||||||
|
// See: Console-linux.cpp
|
||||||
|
|
||||||
|
// find real functions
|
||||||
|
fprintf(stderr,"dfhack: saving real SDL functions\n");
|
||||||
|
_SDL_Init = (int (*)( uint32_t )) dlsym(RTLD_NEXT, "SDL_Init");
|
||||||
|
_SDL_Quit = (void (*)( void )) dlsym(RTLD_NEXT, "SDL_Quit");
|
||||||
|
_SDL_PollEvent = (int (*)(SDL_Event*))dlsym(RTLD_NEXT,"SDL_PollEvent");
|
||||||
|
|
||||||
|
fprintf(stderr,"dfhack: saved real SDL functions\n");
|
||||||
|
// check if we got them
|
||||||
|
if(_SDL_Init && _SDL_Quit && _SDL_PollEvent)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"dfhack: hooking successful\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// bail, this would be a disaster otherwise
|
||||||
|
fprintf(stderr,"dfhack: something went horribly wrong\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
DFHack::Core & c = DFHack::Core::getInstance();
|
||||||
|
//c.Init();
|
||||||
|
|
||||||
|
int ret = _SDL_Init(flags);
|
||||||
|
return ret;
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
/*
|
||||||
|
* MacPool.h
|
||||||
|
* Handles creation and destruction of autorelease pool for DFHack on the Mac
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MACPOOL_H
|
||||||
|
#define MACPOOL_H
|
||||||
|
|
||||||
|
int create_pool();
|
||||||
|
int destroy_pool();
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* MacPool.m
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import "MacPool.h"
|
||||||
|
|
||||||
|
NSAutoreleasePool *thePool;
|
||||||
|
|
||||||
|
int create_pool() {
|
||||||
|
fprintf(stderr,"Creating autorelease pool\n");
|
||||||
|
thePool = [[NSAutoreleasePool alloc] init];
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int destroy_pool() {
|
||||||
|
fprintf(stderr,"Draining and releasing autorelease pool\n");
|
||||||
|
[thePool drain];
|
||||||
|
[thePool release];
|
||||||
|
return 0;
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <dlfcn.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/shm.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/ipc.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#include "DFHack.h"
|
||||||
|
#include "PluginManager.h"
|
||||||
|
#include "Hooks.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Plugin loading functions
|
||||||
|
*/
|
||||||
|
namespace DFHack
|
||||||
|
{
|
||||||
|
DFLibrary * OpenPlugin (const char * filename)
|
||||||
|
{
|
||||||
|
dlerror();
|
||||||
|
DFLibrary * ret = (DFLibrary *) dlopen(filename, RTLD_NOW);
|
||||||
|
if(!ret)
|
||||||
|
{
|
||||||
|
std::cerr << dlerror() << std::endl;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
void * LookupPlugin (DFLibrary * plugin ,const char * function)
|
||||||
|
{
|
||||||
|
return (DFLibrary *) dlsym((void *)plugin, function);
|
||||||
|
}
|
||||||
|
void ClosePlugin (DFLibrary * plugin)
|
||||||
|
{
|
||||||
|
dlclose((void *) plugin);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,229 @@
|
|||||||
|
/*
|
||||||
|
https://github.com/peterix/dfhack
|
||||||
|
Copyright (c) 2009-2011 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Internal.h"
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
|
||||||
|
#include <mach-o/dyld.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
#include <set>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
#include <md5wrapper.h>
|
||||||
|
#include "MemAccess.h"
|
||||||
|
#include "VersionInfoFactory.h"
|
||||||
|
#include "VersionInfo.h"
|
||||||
|
#include "Error.h"
|
||||||
|
#include <string.h>
|
||||||
|
using namespace DFHack;
|
||||||
|
|
||||||
|
Process::Process(VersionInfoFactory * known_versions)
|
||||||
|
{
|
||||||
|
int target_result;
|
||||||
|
|
||||||
|
char path[1024];
|
||||||
|
char *real_path;
|
||||||
|
uint32_t size = sizeof(path);
|
||||||
|
if (_NSGetExecutablePath(path, &size) == 0) {
|
||||||
|
real_path = realpath(path, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
identified = false;
|
||||||
|
my_descriptor = 0;
|
||||||
|
|
||||||
|
md5wrapper md5;
|
||||||
|
uint32_t length;
|
||||||
|
uint8_t first_kb [1024];
|
||||||
|
memset(first_kb, 0, sizeof(first_kb));
|
||||||
|
// get hash of the running DF process
|
||||||
|
string hash = md5.getHashFromFile(real_path, length, (char *) first_kb);
|
||||||
|
// create linux process, add it to the vector
|
||||||
|
VersionInfo * vinfo = known_versions->getVersionInfoByMD5(hash);
|
||||||
|
if(vinfo)
|
||||||
|
{
|
||||||
|
my_descriptor = new VersionInfo(*vinfo);
|
||||||
|
identified = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
char * wd = getcwd(NULL, 0);
|
||||||
|
cerr << "Unable to retrieve version information.\n";
|
||||||
|
cerr << "File: " << real_path << endl;
|
||||||
|
cerr << "MD5: " << hash << endl;
|
||||||
|
cerr << "working dir: " << wd << endl;
|
||||||
|
cerr << "length:" << length << endl;
|
||||||
|
cerr << "1KB hexdump follows:" << endl;
|
||||||
|
for(int i = 0; i < 64; i++)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n",
|
||||||
|
first_kb[i*16],
|
||||||
|
first_kb[i*16+1],
|
||||||
|
first_kb[i*16+2],
|
||||||
|
first_kb[i*16+3],
|
||||||
|
first_kb[i*16+4],
|
||||||
|
first_kb[i*16+5],
|
||||||
|
first_kb[i*16+6],
|
||||||
|
first_kb[i*16+7],
|
||||||
|
first_kb[i*16+8],
|
||||||
|
first_kb[i*16+9],
|
||||||
|
first_kb[i*16+10],
|
||||||
|
first_kb[i*16+11],
|
||||||
|
first_kb[i*16+12],
|
||||||
|
first_kb[i*16+13],
|
||||||
|
first_kb[i*16+14],
|
||||||
|
first_kb[i*16+15]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
free(wd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process::~Process()
|
||||||
|
{
|
||||||
|
// destroy our copy of the memory descriptor
|
||||||
|
delete my_descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
string Process::doReadClassName (void * vptr)
|
||||||
|
{
|
||||||
|
//FIXME: BAD!!!!!
|
||||||
|
char * typeinfo = Process::readPtr(((char *)vptr - 0x4));
|
||||||
|
char * typestring = Process::readPtr(typeinfo + 0x4);
|
||||||
|
string raw = readCString(typestring);
|
||||||
|
size_t start = raw.find_first_of("abcdefghijklmnopqrstuvwxyz");// trim numbers
|
||||||
|
size_t end = raw.length();
|
||||||
|
return raw.substr(start,end-start);
|
||||||
|
}
|
||||||
|
|
||||||
|
//FIXME: cross-reference with ELF segment entries?
|
||||||
|
void Process::getMemRanges( vector<t_memrange> & ranges )
|
||||||
|
{
|
||||||
|
char buffer[1024];
|
||||||
|
char permissions[5]; // r/-, w/-, x/-, p/s, 0
|
||||||
|
|
||||||
|
FILE *mapFile = ::fopen("/proc/self/maps", "r");
|
||||||
|
size_t start, end, offset, device1, device2, node;
|
||||||
|
|
||||||
|
while (fgets(buffer, 1024, mapFile))
|
||||||
|
{
|
||||||
|
t_memrange temp;
|
||||||
|
temp.name[0] = 0;
|
||||||
|
sscanf(buffer, "%zx-%zx %s %zx %2zx:%2zx %zu %[^\n]",
|
||||||
|
&start,
|
||||||
|
&end,
|
||||||
|
(char*)&permissions,
|
||||||
|
&offset, &device1, &device2, &node,
|
||||||
|
(char*)temp.name);
|
||||||
|
temp.start = (void *) start;
|
||||||
|
temp.end = (void *) end;
|
||||||
|
temp.read = permissions[0] == 'r';
|
||||||
|
temp.write = permissions[1] == 'w';
|
||||||
|
temp.execute = permissions[2] == 'x';
|
||||||
|
temp.shared = permissions[3] == 's';
|
||||||
|
temp.valid = true;
|
||||||
|
ranges.push_back(temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t Process::getBase()
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int getdir (string dir, vector<string> &files)
|
||||||
|
{
|
||||||
|
DIR *dp;
|
||||||
|
struct dirent *dirp;
|
||||||
|
if((dp = opendir(dir.c_str())) == NULL)
|
||||||
|
{
|
||||||
|
cout << "Error(" << errno << ") opening " << dir << endl;
|
||||||
|
return errno;
|
||||||
|
}
|
||||||
|
while ((dirp = readdir(dp)) != NULL) {
|
||||||
|
files.push_back(string(dirp->d_name));
|
||||||
|
}
|
||||||
|
closedir(dp);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Process::getThreadIDs(vector<uint32_t> & threads )
|
||||||
|
{
|
||||||
|
stringstream ss;
|
||||||
|
vector<string> subdirs;
|
||||||
|
if(getdir("/proc/self/task/",subdirs) != 0)
|
||||||
|
{
|
||||||
|
//FIXME: needs exceptions. this is a fatal error
|
||||||
|
cerr << "unable to enumerate threads. This is BAD!" << endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
threads.clear();
|
||||||
|
for(size_t i = 0; i < subdirs.size();i++)
|
||||||
|
{
|
||||||
|
uint32_t tid;
|
||||||
|
if(sscanf(subdirs[i].c_str(),"%d", &tid))
|
||||||
|
{
|
||||||
|
threads.push_back(tid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
string Process::getPath()
|
||||||
|
{
|
||||||
|
char path[1024];
|
||||||
|
char *real_path;
|
||||||
|
uint32_t size = sizeof(path);
|
||||||
|
if (_NSGetExecutablePath(path, &size) == 0) {
|
||||||
|
real_path = realpath(path, NULL);
|
||||||
|
}
|
||||||
|
std::string path_string(real_path);
|
||||||
|
int last_slash = path_string.find_last_of("/");
|
||||||
|
std::string directory = path_string.substr(0,last_slash);
|
||||||
|
return directory;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Process::getPID()
|
||||||
|
{
|
||||||
|
return getpid();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Process::setPermisions(const t_memrange & range,const t_memrange &trgrange)
|
||||||
|
{
|
||||||
|
int result;
|
||||||
|
int protect=0;
|
||||||
|
if(trgrange.read)protect|=PROT_READ;
|
||||||
|
if(trgrange.write)protect|=PROT_WRITE;
|
||||||
|
if(trgrange.execute)protect|=PROT_EXEC;
|
||||||
|
result=mprotect((void *)range.start, (size_t)range.end-(size_t)range.start,protect);
|
||||||
|
|
||||||
|
return result==0;
|
||||||
|
}
|
@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
https://github.com/peterix/dfhack
|
||||||
|
Copyright (c) 2009-2011 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Internal.h"
|
||||||
|
#include "Export.h"
|
||||||
|
#include "MiscUtils.h"
|
||||||
|
#include "Error.h"
|
||||||
|
#include "Types.h"
|
||||||
|
|
||||||
|
#ifndef LINUX_BUILD
|
||||||
|
#include <Windows.h>
|
||||||
|
#include "wdirent.h"
|
||||||
|
#else
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <ctime>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
|
||||||
|
int DFHack::getdir(std::string dir, std::vector<std::string> &files)
|
||||||
|
{
|
||||||
|
DIR *dp;
|
||||||
|
struct dirent *dirp;
|
||||||
|
if((dp = opendir(dir.c_str())) == NULL)
|
||||||
|
{
|
||||||
|
return errno;
|
||||||
|
}
|
||||||
|
while ((dirp = readdir(dp)) != NULL) {
|
||||||
|
files.push_back(std::string(dirp->d_name));
|
||||||
|
}
|
||||||
|
closedir(dp);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DFHack::hasEnding (std::string const &fullString, std::string const &ending)
|
||||||
|
{
|
||||||
|
if (fullString.length() > ending.length())
|
||||||
|
{
|
||||||
|
return (0 == fullString.compare (fullString.length() - ending.length(), ending.length(), ending));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
df::general_ref *DFHack::findRef(std::vector<df::general_ref*> &vec, df::general_ref_type type)
|
||||||
|
{
|
||||||
|
for (int i = vec.size()-1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
df::general_ref *ref = vec[i];
|
||||||
|
if (ref->getType() == type)
|
||||||
|
return ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DFHack::removeRef(std::vector<df::general_ref*> &vec, df::general_ref_type type, int id)
|
||||||
|
{
|
||||||
|
for (int i = vec.size()-1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
df::general_ref *ref = vec[i];
|
||||||
|
if (ref->getType() != type || ref->getID() != id)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
vector_erase_at(vec, i);
|
||||||
|
delete ref;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
df::specific_ref *DFHack::findRef(std::vector<df::specific_ref*> &vec, df::specific_ref_type type)
|
||||||
|
{
|
||||||
|
for (int i = vec.size()-1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
df::specific_ref *ref = vec[i];
|
||||||
|
if (ref->type == type)
|
||||||
|
return ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DFHack::removeRef(std::vector<df::specific_ref*> &vec, df::specific_ref_type type, void *ptr)
|
||||||
|
{
|
||||||
|
for (int i = vec.size()-1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
df::specific_ref *ref = vec[i];
|
||||||
|
if (ref->type != type || ref->object != ptr)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
vector_erase_at(vec, i);
|
||||||
|
delete ref;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
@ -0,0 +1,500 @@
|
|||||||
|
local dfhack = dfhack
|
||||||
|
local _ENV = dfhack.BASE_G
|
||||||
|
local buildings = dfhack.buildings
|
||||||
|
|
||||||
|
local utils = require 'utils'
|
||||||
|
|
||||||
|
-- Uninteresting values for filter attributes when reading them from DF memory.
|
||||||
|
-- Differs from the actual defaults of the job_item constructor in allow_artifact.
|
||||||
|
|
||||||
|
buildings.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
|
||||||
|
}
|
||||||
|
|
||||||
|
--[[ Building input material table. ]]
|
||||||
|
|
||||||
|
local building_inputs = {
|
||||||
|
[df.building_type.Chair] = { { item_type=df.item_type.CHAIR, vector_id=df.job_item_vector_id.CHAIR } },
|
||||||
|
[df.building_type.Bed] = { { item_type=df.item_type.BED, vector_id=df.job_item_vector_id.BED } },
|
||||||
|
[df.building_type.Table] = { { item_type=df.item_type.TABLE, vector_id=df.job_item_vector_id.TABLE } },
|
||||||
|
[df.building_type.Coffin] = { { item_type=df.item_type.COFFIN, vector_id=df.job_item_vector_id.COFFIN } },
|
||||||
|
[df.building_type.FarmPlot] = { },
|
||||||
|
[df.building_type.TradeDepot] = { { flags2={ building_material=true, non_economic=true }, quantity=3 } },
|
||||||
|
[df.building_type.Door] = { { item_type=df.item_type.DOOR, vector_id=df.job_item_vector_id.DOOR } },
|
||||||
|
[df.building_type.Floodgate] = {
|
||||||
|
{
|
||||||
|
item_type=df.item_type.FLOODGATE,
|
||||||
|
vector_id=df.job_item_vector_id.FLOODGATE
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[df.building_type.Box] = {
|
||||||
|
{
|
||||||
|
flags1={ empty=true },
|
||||||
|
item_type=df.item_type.BOX,
|
||||||
|
vector_id=df.job_item_vector_id.BOX
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[df.building_type.Weaponrack] = {
|
||||||
|
{
|
||||||
|
item_type=df.item_type.WEAPONRACK,
|
||||||
|
vector_id=df.job_item_vector_id.WEAPONRACK
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[df.building_type.Armorstand] = {
|
||||||
|
{
|
||||||
|
item_type=df.item_type.ARMORSTAND,
|
||||||
|
vector_id=df.job_item_vector_id.ARMORSTAND
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[df.building_type.Cabinet] = {
|
||||||
|
{ item_type=df.item_type.CABINET, vector_id=df.job_item_vector_id.CABINET }
|
||||||
|
},
|
||||||
|
[df.building_type.Statue] = { { item_type=df.item_type.STATUE, vector_id=df.job_item_vector_id.STATUE } },
|
||||||
|
[df.building_type.WindowGlass] = { { item_type=df.item_type.WINDOW, vector_id=df.job_item_vector_id.WINDOW } },
|
||||||
|
[df.building_type.WindowGem] = {
|
||||||
|
{
|
||||||
|
item_type=df.item_type.SMALLGEM,
|
||||||
|
quantity=3,
|
||||||
|
vector_id=df.job_item_vector_id.ANY_GENERIC35
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[df.building_type.Well] = {
|
||||||
|
{
|
||||||
|
item_type=df.item_type.BLOCKS,
|
||||||
|
vector_id=df.job_item_vector_id.ANY_GENERIC35
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name='bucket',
|
||||||
|
flags2={ lye_milk_free=true },
|
||||||
|
item_type=df.item_type.BUCKET,
|
||||||
|
vector_id=df.job_item_vector_id.BUCKET
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name='chain',
|
||||||
|
item_type=df.item_type.CHAIN,
|
||||||
|
vector_id=df.job_item_vector_id.CHAIN
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name='mechanism',
|
||||||
|
item_type=df.item_type.TRAPPARTS,
|
||||||
|
vector_id=df.job_item_vector_id.TRAPPARTS
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[df.building_type.Bridge] = { { flags2={ building_material=true, non_economic=true }, quantity=-1 } },
|
||||||
|
[df.building_type.RoadDirt] = { },
|
||||||
|
[df.building_type.RoadPaved] = { { flags2={ building_material=true, non_economic=true }, quantity=-1 } },
|
||||||
|
[df.building_type.AnimalTrap] = {
|
||||||
|
{
|
||||||
|
flags1={ empty=true },
|
||||||
|
item_type=df.item_type.ANIMALTRAP,
|
||||||
|
vector_id=df.job_item_vector_id.ANIMALTRAP
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[df.building_type.Support] = { { flags2={ building_material=true, non_economic=true } } },
|
||||||
|
[df.building_type.ArcheryTarget] = { { flags2={ building_material=true, non_economic=true } } },
|
||||||
|
[df.building_type.Chain] = { { item_type=df.item_type.CHAIN, vector_id=df.job_item_vector_id.CHAIN } },
|
||||||
|
[df.building_type.Cage] = { { item_type=df.item_type.CAGE, vector_id=df.job_item_vector_id.CAGE } },
|
||||||
|
[df.building_type.Weapon] = { { name='weapon', vector_id=df.job_item_vector_id.ANY_SPIKE } },
|
||||||
|
[df.building_type.ScrewPump] = {
|
||||||
|
{
|
||||||
|
item_type=df.item_type.BLOCKS,
|
||||||
|
vector_id=df.job_item_vector_id.ANY_GENERIC35
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name='screw',
|
||||||
|
flags2={ screw=true },
|
||||||
|
item_type=df.item_type.TRAPCOMP,
|
||||||
|
vector_id=df.job_item_vector_id.ANY_WEAPON
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name='pipe',
|
||||||
|
item_type=df.item_type.PIPE_SECTION,
|
||||||
|
vector_id=df.job_item_vector_id.PIPE_SECTION
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[df.building_type.Construction] = { { flags2={ building_material=true, non_economic=true } } },
|
||||||
|
[df.building_type.Hatch] = {
|
||||||
|
{
|
||||||
|
item_type=df.item_type.HATCH_COVER,
|
||||||
|
vector_id=df.job_item_vector_id.HATCH_COVER
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[df.building_type.GrateWall] = { { item_type=df.item_type.GRATE, vector_id=df.job_item_vector_id.GRATE } },
|
||||||
|
[df.building_type.GrateFloor] = { { item_type=df.item_type.GRATE, vector_id=df.job_item_vector_id.GRATE } },
|
||||||
|
[df.building_type.BarsVertical] = {
|
||||||
|
{ item_type=df.item_type.BAR, vector_id=df.job_item_vector_id.ANY_GENERIC35 }
|
||||||
|
},
|
||||||
|
[df.building_type.BarsFloor] = {
|
||||||
|
{ item_type=df.item_type.BAR, vector_id=df.job_item_vector_id.ANY_GENERIC35 }
|
||||||
|
},
|
||||||
|
[df.building_type.GearAssembly] = {
|
||||||
|
{
|
||||||
|
name='mechanism',
|
||||||
|
item_type=df.item_type.TRAPPARTS,
|
||||||
|
vector_id=df.job_item_vector_id.TRAPPARTS
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[df.building_type.AxleHorizontal] = {
|
||||||
|
{ item_type=df.item_type.WOOD, vector_id=df.job_item_vector_id.WOOD, quantity=-1 }
|
||||||
|
},
|
||||||
|
[df.building_type.AxleVertical] = { { item_type=df.item_type.WOOD, vector_id=df.job_item_vector_id.WOOD } },
|
||||||
|
[df.building_type.WaterWheel] = {
|
||||||
|
{
|
||||||
|
item_type=df.item_type.WOOD,
|
||||||
|
quantity=3,
|
||||||
|
vector_id=df.job_item_vector_id.WOOD
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[df.building_type.Windmill] = {
|
||||||
|
{
|
||||||
|
item_type=df.item_type.WOOD,
|
||||||
|
quantity=4,
|
||||||
|
vector_id=df.job_item_vector_id.WOOD
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[df.building_type.TractionBench] = {
|
||||||
|
{
|
||||||
|
item_type=df.item_type.TRACTION_BENCH,
|
||||||
|
vector_id=df.job_item_vector_id.TRACTION_BENCH
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[df.building_type.Slab] = { { item_type=df.item_type.SLAB } },
|
||||||
|
[df.building_type.NestBox] = { { has_tool_use=df.tool_uses.NEST_BOX, item_type=df.item_type.TOOL } },
|
||||||
|
[df.building_type.Hive] = { { has_tool_use=df.tool_uses.HIVE, item_type=df.item_type.TOOL } },
|
||||||
|
[df.building_type.Rollers] = {
|
||||||
|
{
|
||||||
|
name='mechanism',
|
||||||
|
item_type=df.item_type.TRAPPARTS,
|
||||||
|
quantity=-1,
|
||||||
|
vector_id=df.job_item_vector_id.TRAPPARTS
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name='chain',
|
||||||
|
item_type=df.item_type.CHAIN,
|
||||||
|
vector_id=df.job_item_vector_id.CHAIN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
--[[ Furnace building input material table. ]]
|
||||||
|
|
||||||
|
local furnace_inputs = {
|
||||||
|
[df.furnace_type.WoodFurnace] = { { flags2={ building_material=true, fire_safe=true, non_economic=true } } },
|
||||||
|
[df.furnace_type.Smelter] = { { flags2={ building_material=true, fire_safe=true, non_economic=true } } },
|
||||||
|
[df.furnace_type.GlassFurnace] = { { flags2={ building_material=true, fire_safe=true, non_economic=true } } },
|
||||||
|
[df.furnace_type.Kiln] = { { flags2={ building_material=true, fire_safe=true, non_economic=true } } },
|
||||||
|
[df.furnace_type.MagmaSmelter] = { { flags2={ building_material=true, magma_safe=true, non_economic=true } } },
|
||||||
|
[df.furnace_type.MagmaGlassFurnace] = { { flags2={ building_material=true, magma_safe=true, non_economic=true } } },
|
||||||
|
[df.furnace_type.MagmaKiln] = { { flags2={ building_material=true, magma_safe=true, non_economic=true } } }
|
||||||
|
}
|
||||||
|
|
||||||
|
--[[ Workshop building input material table. ]]
|
||||||
|
|
||||||
|
local workshop_inputs = {
|
||||||
|
[df.workshop_type.Carpenters] = { { flags2={ building_material=true, non_economic=true } } },
|
||||||
|
[df.workshop_type.Farmers] = { { flags2={ building_material=true, non_economic=true } } },
|
||||||
|
[df.workshop_type.Masons] = { { flags2={ building_material=true, non_economic=true } } },
|
||||||
|
[df.workshop_type.Craftsdwarfs] = { { flags2={ building_material=true, non_economic=true } } },
|
||||||
|
[df.workshop_type.Jewelers] = { { flags2={ building_material=true, non_economic=true } } },
|
||||||
|
[df.workshop_type.MetalsmithsForge] = {
|
||||||
|
{
|
||||||
|
name='anvil',
|
||||||
|
flags2={ fire_safe=true },
|
||||||
|
item_type=df.item_type.ANVIL,
|
||||||
|
vector_id=df.job_item_vector_id.ANVIL
|
||||||
|
},
|
||||||
|
{ flags2={ building_material=true, fire_safe=true, non_economic=true } }
|
||||||
|
},
|
||||||
|
[df.workshop_type.MagmaForge] = {
|
||||||
|
{
|
||||||
|
name='anvil',
|
||||||
|
flags2={ magma_safe=true },
|
||||||
|
item_type=df.item_type.ANVIL,
|
||||||
|
vector_id=df.job_item_vector_id.ANVIL
|
||||||
|
},
|
||||||
|
{ flags2={ building_material=true, magma_safe=true, non_economic=true } }
|
||||||
|
},
|
||||||
|
[df.workshop_type.Bowyers] = { { flags2={ building_material=true, non_economic=true } } },
|
||||||
|
[df.workshop_type.Mechanics] = { { flags2={ building_material=true, non_economic=true } } },
|
||||||
|
[df.workshop_type.Siege] = { { flags2={ building_material=true, non_economic=true }, quantity=3 } },
|
||||||
|
[df.workshop_type.Butchers] = { { flags2={ building_material=true, non_economic=true } } },
|
||||||
|
[df.workshop_type.Leatherworks] = { { flags2={ building_material=true, non_economic=true } } },
|
||||||
|
[df.workshop_type.Tanners] = { { flags2={ building_material=true, non_economic=true } } },
|
||||||
|
[df.workshop_type.Clothiers] = { { flags2={ building_material=true, non_economic=true } } },
|
||||||
|
[df.workshop_type.Fishery] = { { flags2={ building_material=true, non_economic=true } } },
|
||||||
|
[df.workshop_type.Still] = { { flags2={ building_material=true, non_economic=true } } },
|
||||||
|
[df.workshop_type.Loom] = { { flags2={ building_material=true, non_economic=true } } },
|
||||||
|
[df.workshop_type.Quern] = { { item_type=df.item_type.QUERN, vector_id=df.job_item_vector_id.QUERN } },
|
||||||
|
[df.workshop_type.Kennels] = { { flags2={ building_material=true, non_economic=true } } },
|
||||||
|
[df.workshop_type.Kitchen] = { { flags2={ building_material=true, non_economic=true } } },
|
||||||
|
[df.workshop_type.Ashery] = {
|
||||||
|
{
|
||||||
|
item_type=df.item_type.BLOCKS,
|
||||||
|
vector_id=df.job_item_vector_id.ANY_GENERIC35
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name='barrel',
|
||||||
|
flags1={ empty=true },
|
||||||
|
item_type=df.item_type.BARREL,
|
||||||
|
vector_id=df.job_item_vector_id.BARREL
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name='bucket',
|
||||||
|
flags2={ lye_milk_free=true },
|
||||||
|
item_type=df.item_type.BUCKET,
|
||||||
|
vector_id=df.job_item_vector_id.BUCKET
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[df.workshop_type.Dyers] = {
|
||||||
|
{
|
||||||
|
name='barrel',
|
||||||
|
flags1={ empty=true },
|
||||||
|
item_type=df.item_type.BARREL,
|
||||||
|
vector_id=df.job_item_vector_id.BARREL
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name='bucket',
|
||||||
|
flags2={ lye_milk_free=true },
|
||||||
|
item_type=df.item_type.BUCKET,
|
||||||
|
vector_id=df.job_item_vector_id.BUCKET
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[df.workshop_type.Millstone] = {
|
||||||
|
{
|
||||||
|
item_type=df.item_type.MILLSTONE,
|
||||||
|
vector_id=df.job_item_vector_id.MILLSTONE
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name='mechanism',
|
||||||
|
item_type=df.item_type.TRAPPARTS,
|
||||||
|
vector_id=df.job_item_vector_id.TRAPPARTS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
--[[ Trap building input material table. ]]
|
||||||
|
|
||||||
|
local trap_inputs = {
|
||||||
|
[df.trap_type.StoneFallTrap] = {
|
||||||
|
{
|
||||||
|
name='mechanism',
|
||||||
|
item_type=df.item_type.TRAPPARTS,
|
||||||
|
vector_id=df.job_item_vector_id.TRAPPARTS
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[df.trap_type.WeaponTrap] = {
|
||||||
|
{
|
||||||
|
name='mechanism',
|
||||||
|
item_type=df.item_type.TRAPPARTS,
|
||||||
|
vector_id=df.job_item_vector_id.TRAPPARTS
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name='weapon',
|
||||||
|
vector_id=df.job_item_vector_id.ANY_WEAPON
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[df.trap_type.Lever] = {
|
||||||
|
{
|
||||||
|
name='mechanism',
|
||||||
|
item_type=df.item_type.TRAPPARTS,
|
||||||
|
vector_id=df.job_item_vector_id.TRAPPARTS
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[df.trap_type.PressurePlate] = {
|
||||||
|
{
|
||||||
|
name='mechanism',
|
||||||
|
item_type=df.item_type.TRAPPARTS,
|
||||||
|
vector_id=df.job_item_vector_id.TRAPPARTS
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[df.trap_type.CageTrap] = {
|
||||||
|
{
|
||||||
|
name='mechanism',
|
||||||
|
item_type=df.item_type.TRAPPARTS,
|
||||||
|
vector_id=df.job_item_vector_id.TRAPPARTS
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[df.trap_type.TrackStop] = { { flags2={ building_material=true, non_economic=true } } }
|
||||||
|
}
|
||||||
|
|
||||||
|
--[[ Functions for lookup in tables. ]]
|
||||||
|
|
||||||
|
local function get_custom_inputs(custom)
|
||||||
|
local defn = df.building_def.find(custom)
|
||||||
|
if defn ~= nil then
|
||||||
|
return utils.clone_with_default(defn.build_items, buildings.input_filter_defaults)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_inputs_by_type(type,subtype,custom)
|
||||||
|
if type == df.building_type.Workshop then
|
||||||
|
if subtype == df.workshop_type.Custom then
|
||||||
|
return get_custom_inputs(custom)
|
||||||
|
else
|
||||||
|
return workshop_inputs[subtype]
|
||||||
|
end
|
||||||
|
elseif type == df.building_type.Furnace then
|
||||||
|
if subtype == df.furnace_type.Custom then
|
||||||
|
return get_custom_inputs(custom)
|
||||||
|
else
|
||||||
|
return furnace_inputs[subtype]
|
||||||
|
end
|
||||||
|
elseif type == df.building_type.Trap then
|
||||||
|
return trap_inputs[subtype]
|
||||||
|
else
|
||||||
|
return building_inputs[type]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function augment_input(input, argtable)
|
||||||
|
local rv = {}
|
||||||
|
local arg = argtable[input.name or 'material']
|
||||||
|
|
||||||
|
if arg then
|
||||||
|
utils.assign(rv, arg)
|
||||||
|
end
|
||||||
|
|
||||||
|
utils.assign(rv, input)
|
||||||
|
|
||||||
|
if rv.mat_index and safe_index(rv, 'flags2', 'non_economic') then
|
||||||
|
rv.flags2.non_economic = false
|
||||||
|
end
|
||||||
|
|
||||||
|
rv.new = true
|
||||||
|
rv.name = nil
|
||||||
|
return rv
|
||||||
|
end
|
||||||
|
|
||||||
|
function buildings.getFiltersByType(argtable,type,subtype,custom)
|
||||||
|
local inputs = get_inputs_by_type(type,subtype,custom)
|
||||||
|
if not inputs then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local rv = {}
|
||||||
|
for i,v in ipairs(inputs) do
|
||||||
|
rv[i] = augment_input(v, argtable)
|
||||||
|
end
|
||||||
|
return rv
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Wraps all steps necessary to create a building with
|
||||||
|
a construct job into one function.
|
||||||
|
|
||||||
|
dfhack.buildings.constructBuilding{
|
||||||
|
-- Position:
|
||||||
|
pos = { x = ..., y = ..., z = ... },
|
||||||
|
-- OR
|
||||||
|
x = ..., y = ..., z = ...,
|
||||||
|
|
||||||
|
-- Type:
|
||||||
|
type = df.building_type.FOO, subtype = ..., custom = ...,
|
||||||
|
|
||||||
|
-- Field initialization:
|
||||||
|
fields = { ... },
|
||||||
|
|
||||||
|
-- Size and orientation:
|
||||||
|
width = ..., height = ..., direction = ...,
|
||||||
|
|
||||||
|
-- Abort if not all tiles in the rectangle are available:
|
||||||
|
full_rectangle = true,
|
||||||
|
|
||||||
|
-- Materials:
|
||||||
|
items = { item, item ... },
|
||||||
|
-- OR
|
||||||
|
filters = { { ... }, { ... }... }
|
||||||
|
-- OR
|
||||||
|
abstract = true
|
||||||
|
-- OR
|
||||||
|
material = { filter_properties... }
|
||||||
|
mechanism = { filter_properties... }
|
||||||
|
barrel, bucket, chain, anvil, screw, pipe
|
||||||
|
}
|
||||||
|
|
||||||
|
Returns: the created building, or 'nil, error'
|
||||||
|
--]]
|
||||||
|
|
||||||
|
function buildings.constructBuilding(info)
|
||||||
|
local btype = info.type
|
||||||
|
local subtype = info.subtype or -1
|
||||||
|
local custom = info.custom or -1
|
||||||
|
local filters = info.filters
|
||||||
|
|
||||||
|
if not (info.pos or info.x) then
|
||||||
|
error('position is required')
|
||||||
|
end
|
||||||
|
if not (info.abstract or info.items or filters) then
|
||||||
|
filters = buildings.getFiltersByType(info,btype,subtype,custom)
|
||||||
|
if not filters then
|
||||||
|
error('one of items, filters or abstract is required')
|
||||||
|
end
|
||||||
|
elseif filters then
|
||||||
|
for _,v in ipairs(filters) do
|
||||||
|
v.new = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if type(btype) ~= 'number' or not df.building_type[btype] then
|
||||||
|
error('Invalid building type: '..tostring(btype))
|
||||||
|
end
|
||||||
|
|
||||||
|
local pos = info.pos or xyz2pos(info.x, info.y, info.z)
|
||||||
|
|
||||||
|
local instance = buildings.allocInstance(pos, btype, subtype, custom)
|
||||||
|
if not instance then
|
||||||
|
error('Could not create building of type '..df.building_type[btype])
|
||||||
|
end
|
||||||
|
|
||||||
|
local to_delete = instance
|
||||||
|
return dfhack.with_finalize(
|
||||||
|
function()
|
||||||
|
df.delete(to_delete)
|
||||||
|
end,
|
||||||
|
function()
|
||||||
|
if info.fields then
|
||||||
|
instance:assign(info.fields)
|
||||||
|
end
|
||||||
|
local ok,w,h,area,r_area = buildings.setSize(
|
||||||
|
instance,info.width,info.height,info.direction
|
||||||
|
)
|
||||||
|
if not ok then
|
||||||
|
return nil, "cannot place at this position"
|
||||||
|
end
|
||||||
|
if info.full_rectangle and area ~= r_area then
|
||||||
|
return nil, "not all tiles can be used"
|
||||||
|
end
|
||||||
|
if info.abstract then
|
||||||
|
ok = buildings.constructAbstract(instance)
|
||||||
|
elseif info.items then
|
||||||
|
ok = buildings.constructWithItems(instance, info.items)
|
||||||
|
else
|
||||||
|
ok = buildings.constructWithFilters(instance, filters)
|
||||||
|
end
|
||||||
|
if not ok then
|
||||||
|
return nil, "could not construct the building"
|
||||||
|
end
|
||||||
|
-- Success
|
||||||
|
to_delete = nil
|
||||||
|
return instance
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
return buildings
|
@ -0,0 +1,234 @@
|
|||||||
|
--[[ DataDumper.lua
|
||||||
|
Copyright (c) 2007 Olivetti-Engineering SA
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person
|
||||||
|
obtaining a copy of this software and associated documentation
|
||||||
|
files (the "Software"), to deal in the Software without
|
||||||
|
restriction, including without limitation the rights to use,
|
||||||
|
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
]]
|
||||||
|
|
||||||
|
local _ENV = mkmodule('dumper')
|
||||||
|
|
||||||
|
local dumplua_closure = [[
|
||||||
|
local closures = {}
|
||||||
|
local function closure(t)
|
||||||
|
closures[#closures+1] = t
|
||||||
|
t[1] = assert(loadstring(t[1]))
|
||||||
|
return t[1]
|
||||||
|
end
|
||||||
|
|
||||||
|
for _,t in pairs(closures) do
|
||||||
|
for i = 2,#t do
|
||||||
|
debug.setupvalue(t[1], i-1, t[i])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
]]
|
||||||
|
|
||||||
|
local lua_reserved_keywords = {
|
||||||
|
'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for',
|
||||||
|
'function', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat',
|
||||||
|
'return', 'then', 'true', 'until', 'while' }
|
||||||
|
|
||||||
|
local function keys(t)
|
||||||
|
local res = {}
|
||||||
|
local oktypes = { stringstring = true, numbernumber = true }
|
||||||
|
local function cmpfct(a,b)
|
||||||
|
if oktypes[type(a)..type(b)] then
|
||||||
|
return a < b
|
||||||
|
else
|
||||||
|
return type(a) < type(b)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for k in pairs(t) do
|
||||||
|
res[#res+1] = k
|
||||||
|
end
|
||||||
|
table.sort(res, cmpfct)
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
|
||||||
|
local c_functions = {}
|
||||||
|
for _,lib in pairs{'_G', 'string', 'table', 'math',
|
||||||
|
'io', 'os', 'coroutine', 'package', 'debug'} do
|
||||||
|
local t = _G[lib] or {}
|
||||||
|
lib = lib .. "."
|
||||||
|
if lib == "_G." then lib = "" end
|
||||||
|
for k,v in pairs(t) do
|
||||||
|
if type(v) == 'function' and not pcall(string.dump, v) then
|
||||||
|
c_functions[v] = lib..k
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function DataDumper(value, varname, fastmode, ident, indent_step)
|
||||||
|
indent_step = indent_step or 2
|
||||||
|
local defined, dumplua = {}
|
||||||
|
-- Local variables for speed optimization
|
||||||
|
local string_format, type, string_dump, string_rep =
|
||||||
|
string.format, type, string.dump, string.rep
|
||||||
|
local tostring, pairs, table_concat =
|
||||||
|
tostring, pairs, table.concat
|
||||||
|
local keycache, strvalcache, out, closure_cnt = {}, {}, {}, 0
|
||||||
|
setmetatable(strvalcache, {__index = function(t,value)
|
||||||
|
local res = string_format('%q', value)
|
||||||
|
t[value] = res
|
||||||
|
return res
|
||||||
|
end})
|
||||||
|
local fcts = {
|
||||||
|
string = function(value) return strvalcache[value] end,
|
||||||
|
number = function(value) return value end,
|
||||||
|
boolean = function(value) return tostring(value) end,
|
||||||
|
['nil'] = function(value) return 'nil' end,
|
||||||
|
['function'] = function(value)
|
||||||
|
return string_format("loadstring(%q)", string_dump(value))
|
||||||
|
end,
|
||||||
|
userdata = function() error("Cannot dump userdata") end,
|
||||||
|
thread = function() error("Cannot dump threads") end,
|
||||||
|
}
|
||||||
|
local function test_defined(value, path)
|
||||||
|
if defined[value] then
|
||||||
|
if path:match("^getmetatable.*%)$") then
|
||||||
|
out[#out+1] = string_format("s%s, %s)\n", path:sub(2,-2), defined[value])
|
||||||
|
else
|
||||||
|
out[#out+1] = path .. " = " .. defined[value] .. "\n"
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
defined[value] = path
|
||||||
|
end
|
||||||
|
local function make_key(t, key)
|
||||||
|
local s
|
||||||
|
if type(key) == 'string' and key:match('^[_%a][_%w]*$') then
|
||||||
|
s = key .. "="
|
||||||
|
else
|
||||||
|
s = "[" .. dumplua(key, 0) .. "]="
|
||||||
|
end
|
||||||
|
t[key] = s
|
||||||
|
return s
|
||||||
|
end
|
||||||
|
for _,k in ipairs(lua_reserved_keywords) do
|
||||||
|
keycache[k] = '["'..k..'"] = '
|
||||||
|
end
|
||||||
|
if fastmode then
|
||||||
|
fcts.table = function (value)
|
||||||
|
-- Table value
|
||||||
|
local numidx = 1
|
||||||
|
out[#out+1] = "{"
|
||||||
|
for key,val in pairs(value) do
|
||||||
|
if key == numidx then
|
||||||
|
numidx = numidx + 1
|
||||||
|
else
|
||||||
|
out[#out+1] = keycache[key]
|
||||||
|
end
|
||||||
|
local str = dumplua(val)
|
||||||
|
out[#out+1] = str..","
|
||||||
|
end
|
||||||
|
if string.sub(out[#out], -1) == "," then
|
||||||
|
out[#out] = string.sub(out[#out], 1, -2);
|
||||||
|
end
|
||||||
|
out[#out+1] = "}"
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
else
|
||||||
|
fcts.table = function (value, ident, path)
|
||||||
|
if test_defined(value, path) then return "nil" end
|
||||||
|
-- Table value
|
||||||
|
local sep, str, numidx, totallen = " ", {}, 1, 0
|
||||||
|
local meta, metastr = (debug or getfenv()).getmetatable(value)
|
||||||
|
if meta then
|
||||||
|
ident = ident + 1
|
||||||
|
metastr = dumplua(meta, ident, "getmetatable("..path..")")
|
||||||
|
totallen = totallen + #metastr + 16
|
||||||
|
end
|
||||||
|
for _,key in pairs(keys(value)) do
|
||||||
|
local val = value[key]
|
||||||
|
local s = ""
|
||||||
|
local subpath = path
|
||||||
|
if key == numidx then
|
||||||
|
subpath = subpath .. "[" .. numidx .. "]"
|
||||||
|
numidx = numidx + 1
|
||||||
|
else
|
||||||
|
s = keycache[key]
|
||||||
|
if not s:match "^%[" then subpath = subpath .. "." end
|
||||||
|
subpath = subpath .. s:gsub("%s*=%s*$","")
|
||||||
|
end
|
||||||
|
s = s .. dumplua(val, ident+1, subpath)
|
||||||
|
str[#str+1] = s
|
||||||
|
totallen = totallen + #s + 2
|
||||||
|
end
|
||||||
|
if totallen > 80 then
|
||||||
|
sep = "\n" .. string_rep(' ', indent_step*(ident+1))
|
||||||
|
end
|
||||||
|
str = "{"..sep..table_concat(str, ","..sep).." "..sep:sub(1,-1-indent_step).."}"
|
||||||
|
if meta then
|
||||||
|
sep = sep:sub(1,-3)
|
||||||
|
return "setmetatable("..sep..str..","..sep..metastr..sep:sub(1,-3)..")"
|
||||||
|
end
|
||||||
|
return str
|
||||||
|
end
|
||||||
|
fcts['function'] = function (value, ident, path)
|
||||||
|
if test_defined(value, path) then return "nil" end
|
||||||
|
if c_functions[value] then
|
||||||
|
return c_functions[value]
|
||||||
|
elseif debug == nil or debug.getupvalue(value, 1) == nil then
|
||||||
|
return string_format("loadstring(%q)", string_dump(value))
|
||||||
|
end
|
||||||
|
closure_cnt = closure_cnt + 1
|
||||||
|
local res = {string.dump(value)}
|
||||||
|
for i = 1,math.huge do
|
||||||
|
local name, v = debug.getupvalue(value,i)
|
||||||
|
if name == nil then break end
|
||||||
|
res[i+1] = v
|
||||||
|
end
|
||||||
|
return "closure " .. dumplua(res, ident, "closures["..closure_cnt.."]")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
function dumplua(value, ident, path)
|
||||||
|
return fcts[type(value)](value, ident, path)
|
||||||
|
end
|
||||||
|
if varname == nil then
|
||||||
|
varname = "return "
|
||||||
|
elseif varname:match("^[%a_][%w_]*$") then
|
||||||
|
varname = varname .. " = "
|
||||||
|
end
|
||||||
|
if fastmode then
|
||||||
|
setmetatable(keycache, {__index = make_key })
|
||||||
|
out[1] = varname
|
||||||
|
table.insert(out,dumplua(value, 0))
|
||||||
|
return table.concat(out)
|
||||||
|
else
|
||||||
|
setmetatable(keycache, {__index = make_key })
|
||||||
|
local items = {}
|
||||||
|
for i=1,10 do items[i] = '' end
|
||||||
|
items[3] = dumplua(value, ident or 0, "t")
|
||||||
|
if closure_cnt > 0 then
|
||||||
|
items[1], items[6] = dumplua_closure:match("(.*\n)\n(.*)")
|
||||||
|
out[#out+1] = ""
|
||||||
|
end
|
||||||
|
if #out > 0 then
|
||||||
|
items[2], items[4] = "local t = ", "\n"
|
||||||
|
items[5] = table.concat(out)
|
||||||
|
items[7] = varname .. "t"
|
||||||
|
else
|
||||||
|
items[2] = varname
|
||||||
|
end
|
||||||
|
return table.concat(items)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return _ENV
|
@ -1 +1 @@
|
|||||||
Subproject commit f649d31001e6023a9df5fe83c7971c17afe0d87d
|
Subproject commit c381884664c71adefbec44258a734def2c88dacc
|
@ -0,0 +1,116 @@
|
|||||||
|
#include "Core.h"
|
||||||
|
#include "Console.h"
|
||||||
|
#include "Export.h"
|
||||||
|
#include "PluginManager.h"
|
||||||
|
|
||||||
|
#include "DataDefs.h"
|
||||||
|
#include "df/world.h"
|
||||||
|
#include "df/ui.h"
|
||||||
|
#include "df/building_nest_boxst.h"
|
||||||
|
#include "df/building_type.h"
|
||||||
|
#include "df/global_objects.h"
|
||||||
|
#include "df/item.h"
|
||||||
|
#include "df/unit.h"
|
||||||
|
#include "df/building.h"
|
||||||
|
#include "df/items_other_id.h"
|
||||||
|
#include "df/creature_raw.h"
|
||||||
|
#include "modules/MapCache.h"
|
||||||
|
#include "modules/Items.h"
|
||||||
|
|
||||||
|
|
||||||
|
using std::vector;
|
||||||
|
using std::string;
|
||||||
|
using std::endl;
|
||||||
|
using namespace DFHack;
|
||||||
|
using namespace df::enums;
|
||||||
|
|
||||||
|
using df::global::world;
|
||||||
|
using df::global::ui;
|
||||||
|
|
||||||
|
static command_result nestboxes(color_ostream &out, vector <string> & parameters);
|
||||||
|
|
||||||
|
DFHACK_PLUGIN("nestboxes");
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
|
||||||
|
{
|
||||||
|
if (world && ui) {
|
||||||
|
commands.push_back(
|
||||||
|
PluginCommand("nestboxes", "Derp.",
|
||||||
|
nestboxes, false,
|
||||||
|
"Derp.\n"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||||
|
{
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static command_result nestboxes(color_ostream &out, vector <string> & parameters)
|
||||||
|
{
|
||||||
|
CoreSuspender suspend;
|
||||||
|
bool clean = false;
|
||||||
|
int dump_count = 0;
|
||||||
|
int good_egg = 0;
|
||||||
|
|
||||||
|
if (parameters.size() == 1 && parameters[0] == "clean")
|
||||||
|
{
|
||||||
|
clean = true;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < world->buildings.all.size(); ++i)
|
||||||
|
{
|
||||||
|
df::building *build = world->buildings.all[i];
|
||||||
|
auto type = build->getType();
|
||||||
|
if (df::enums::building_type::NestBox == type)
|
||||||
|
{
|
||||||
|
bool needs_clean = false;
|
||||||
|
df::building_nest_boxst *nb = virtual_cast<df::building_nest_boxst>(build);
|
||||||
|
out << "Nestbox at (" << nb->x1 << "," << nb->y1 << ","<< nb->z << "): claimed-by " << nb->claimed_by << ", contained item count " << nb->contained_items.size() << " (" << nb->anon_1 << ")" << endl;
|
||||||
|
if (nb->contained_items.size() > 1)
|
||||||
|
needs_clean = true;
|
||||||
|
if (nb->claimed_by != -1)
|
||||||
|
{
|
||||||
|
df::unit* u = df::unit::find(nb->claimed_by);
|
||||||
|
if (u)
|
||||||
|
{
|
||||||
|
out << " Claimed by ";
|
||||||
|
if (u->name.has_name)
|
||||||
|
out << u->name.first_name << ", ";
|
||||||
|
df::creature_raw *raw = df::global::world->raws.creatures.all[u->race];
|
||||||
|
out << raw->creature_id
|
||||||
|
<< ", pregnancy timer " << u->relations.pregnancy_timer << endl;
|
||||||
|
if (u->relations.pregnancy_timer > 0)
|
||||||
|
needs_clean = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int j = 1; j < nb->contained_items.size(); j++)
|
||||||
|
{
|
||||||
|
df::item* item = nb->contained_items[j]->item;
|
||||||
|
if (needs_clean) {
|
||||||
|
if (clean && !item->flags.bits.dump)
|
||||||
|
{
|
||||||
|
item->flags.bits.dump = 1;
|
||||||
|
dump_count += item->getStackSize();
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
good_egg += item->getStackSize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clean)
|
||||||
|
{
|
||||||
|
out << dump_count << " eggs dumped." << endl;
|
||||||
|
}
|
||||||
|
out << good_egg << " fertile eggs found." << endl;
|
||||||
|
|
||||||
|
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,163 @@
|
|||||||
|
// Produces a list of materials available on the map.
|
||||||
|
// Options:
|
||||||
|
// -a : show unrevealed tiles
|
||||||
|
// -p : don't show plants
|
||||||
|
// -s : don't show slade
|
||||||
|
// -t : don't show demon temple
|
||||||
|
|
||||||
|
//#include <cstdlib>
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <map>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
#include "Core.h"
|
||||||
|
#include "Console.h"
|
||||||
|
#include "Export.h"
|
||||||
|
#include "PluginManager.h"
|
||||||
|
#include "modules/MapCache.h"
|
||||||
|
|
||||||
|
#include "MiscUtils.h"
|
||||||
|
|
||||||
|
#include "DataDefs.h"
|
||||||
|
#include "df/world.h"
|
||||||
|
#include "df/world_data.h"
|
||||||
|
#include "df/world_region_details.h"
|
||||||
|
#include "df/world_geo_biome.h"
|
||||||
|
#include "df/world_geo_layer.h"
|
||||||
|
#include "df/inclusion_type.h"
|
||||||
|
#include "df/viewscreen_choose_start_sitest.h"
|
||||||
|
|
||||||
|
using namespace DFHack;
|
||||||
|
using namespace df::enums;
|
||||||
|
using df::global::world;
|
||||||
|
using df::coord2d;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
command_result rprobe (color_ostream &out, vector <string> & parameters);
|
||||||
|
|
||||||
|
DFHACK_PLUGIN("rprobe");
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||||
|
{
|
||||||
|
commands.push_back(PluginCommand(
|
||||||
|
"rprobe", "Display assorted region information from embark screen",
|
||||||
|
rprobe, false,
|
||||||
|
"Display assorted region information from embark screen\n"
|
||||||
|
));
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||||
|
{
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
command_result rprobe (color_ostream &out, vector <string> & parameters)
|
||||||
|
{
|
||||||
|
CoreSuspender suspend;
|
||||||
|
|
||||||
|
bool set = false;
|
||||||
|
int to_set, set_field, set_val;
|
||||||
|
|
||||||
|
// Embark screen active: estimate using world geology data
|
||||||
|
VIRTUAL_CAST_VAR(screen, df::viewscreen_choose_start_sitest, Core::getTopViewscreen());
|
||||||
|
|
||||||
|
if (!screen)
|
||||||
|
return CR_WRONG_USAGE;
|
||||||
|
|
||||||
|
if (!world || !world->world_data)
|
||||||
|
{
|
||||||
|
out.printerr("World data is not available.\n");
|
||||||
|
return CR_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (parameters.size() == 2)
|
||||||
|
{
|
||||||
|
if (parameters[0] == "wet")
|
||||||
|
set_field = 0;
|
||||||
|
else if (parameters[0] == "veg")
|
||||||
|
set_field = 1;
|
||||||
|
else if (parameters[0] == "tem")
|
||||||
|
set_field = 2;
|
||||||
|
else if (parameters[0] == "evi")
|
||||||
|
set_field = 3;
|
||||||
|
else if (parameters[0] == "hil")
|
||||||
|
set_field = 4;
|
||||||
|
else if (parameters[0] == "sav")
|
||||||
|
set_field = 5;
|
||||||
|
else if (parameters[0] == "sal")
|
||||||
|
set_field = 6;
|
||||||
|
else
|
||||||
|
return CR_WRONG_USAGE;
|
||||||
|
|
||||||
|
if (screen->biome_highlighted)
|
||||||
|
to_set = screen->biome_idx;
|
||||||
|
else
|
||||||
|
to_set = 0;
|
||||||
|
|
||||||
|
set = true;
|
||||||
|
set_val = atoi(parameters[1].c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
df::world_data *data = world->world_data;
|
||||||
|
coord2d cur_region = screen->region_pos;
|
||||||
|
|
||||||
|
// Compute biomes
|
||||||
|
for (int i = 0; i < screen->biome_rgn.size(); i++)
|
||||||
|
{
|
||||||
|
coord2d rg = screen->biome_rgn[i];
|
||||||
|
|
||||||
|
df::world_data::T_region_map* rd = &data->region_map[rg.x][rg.y];
|
||||||
|
|
||||||
|
if (set && i == to_set) {
|
||||||
|
if (set_field == 0)
|
||||||
|
rd->wetness = set_val;
|
||||||
|
else if (set_field == 1)
|
||||||
|
rd->vegetation = set_val;
|
||||||
|
else if (set_field == 2)
|
||||||
|
rd->temperature = set_val;
|
||||||
|
else if (set_field == 3)
|
||||||
|
rd->evilness = set_val;
|
||||||
|
else if (set_field == 4)
|
||||||
|
rd->hilliness = set_val;
|
||||||
|
else if (set_field == 5)
|
||||||
|
rd->savagery = set_val;
|
||||||
|
else if (set_field == 6)
|
||||||
|
rd->saltiness = set_val;
|
||||||
|
}
|
||||||
|
|
||||||
|
out << i << ": x = " << rg.x << ", y = " << rg.y;
|
||||||
|
|
||||||
|
out <<
|
||||||
|
" region_id: " << rd->region_id <<
|
||||||
|
" geo_index: " << rd->geo_index <<
|
||||||
|
" landmass_id: " << rd->landmass_id <<
|
||||||
|
" flags: " << hex << rd->flags.as_int() << dec << endl;
|
||||||
|
out <<
|
||||||
|
"wet: " << rd->wetness << " " <<
|
||||||
|
"veg: " << rd->vegetation << " " <<
|
||||||
|
"tem: " << rd->temperature << " " <<
|
||||||
|
"evi: " << rd->evilness << " " <<
|
||||||
|
"hil: " << rd->hilliness << " " <<
|
||||||
|
"sav: " << rd->savagery << " " <<
|
||||||
|
"sal: " << rd->saltiness;
|
||||||
|
|
||||||
|
int32_t *p = (int32_t *)rd;
|
||||||
|
int c = sizeof(*rd) / sizeof(int32_t);
|
||||||
|
for (int j = 0; j < c; j++) {
|
||||||
|
if (j % 8 == 0)
|
||||||
|
out << endl << setfill('0') << setw(8) << hex << (int)(rd+j) << ": ";
|
||||||
|
out << " " << setfill('0') << setw(8) << hex << p[j];
|
||||||
|
}
|
||||||
|
out << setfill(' ') << setw(0) << dec << endl;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return CR_OK;
|
||||||
|
}
|
@ -0,0 +1,282 @@
|
|||||||
|
#include "Core.h"
|
||||||
|
#include "Console.h"
|
||||||
|
#include "Export.h"
|
||||||
|
#include "PluginManager.h"
|
||||||
|
|
||||||
|
#include "DataDefs.h"
|
||||||
|
#include "df/world.h"
|
||||||
|
#include "df/ui.h"
|
||||||
|
#include "df/building_stockpilest.h"
|
||||||
|
#include "df/global_objects.h"
|
||||||
|
#include "df/item.h"
|
||||||
|
#include "df/unit.h"
|
||||||
|
#include "df/building.h"
|
||||||
|
#include "df/items_other_id.h"
|
||||||
|
#include "df/item_stockpile_ref.h"
|
||||||
|
#include "modules/MapCache.h"
|
||||||
|
#include "modules/Items.h"
|
||||||
|
|
||||||
|
|
||||||
|
using std::vector;
|
||||||
|
using std::string;
|
||||||
|
using std::endl;
|
||||||
|
using namespace DFHack;
|
||||||
|
using namespace df::enums;
|
||||||
|
|
||||||
|
using df::global::world;
|
||||||
|
using df::global::ui;
|
||||||
|
using df::global::selection_rect;
|
||||||
|
|
||||||
|
using df::building_stockpilest;
|
||||||
|
|
||||||
|
static command_result copystock(color_ostream &out, vector <string> & parameters);
|
||||||
|
static command_result stockcheck(color_ostream &out, vector <string> & parameters);
|
||||||
|
static bool copystock_guard(df::viewscreen *top);
|
||||||
|
|
||||||
|
DFHACK_PLUGIN("stockcheck");
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
|
||||||
|
{
|
||||||
|
if (world && ui) {
|
||||||
|
commands.push_back(
|
||||||
|
PluginCommand("stockcheck", "Check for unprotected rottable items.",
|
||||||
|
stockcheck, false,
|
||||||
|
"Scan world for items that are susceptible to rot. Currently just lists the items.\n"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||||
|
{
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StockpileInfo {
|
||||||
|
building_stockpilest* sp;
|
||||||
|
int size;
|
||||||
|
int free;
|
||||||
|
int x1, x2, y1, y2, z;
|
||||||
|
|
||||||
|
public:
|
||||||
|
StockpileInfo(building_stockpilest *sp_) : sp(sp_)
|
||||||
|
{
|
||||||
|
MapExtras::MapCache mc;
|
||||||
|
|
||||||
|
z = sp_->z;
|
||||||
|
x1 = sp_->room.x;
|
||||||
|
x2 = sp_->room.x + sp_->room.width;
|
||||||
|
y1 = sp_->room.y;
|
||||||
|
y2 = sp_->room.y + sp_->room.height;
|
||||||
|
int e = 0;
|
||||||
|
size = 0;
|
||||||
|
free = 0;
|
||||||
|
for (int y = y1; y < y2; y++)
|
||||||
|
for (int x = x1; x < x2; x++)
|
||||||
|
if (sp_->room.extents[e++] == 1)
|
||||||
|
{
|
||||||
|
size++;
|
||||||
|
DFCoord cursor (x,y,z);
|
||||||
|
uint32_t blockX = x / 16;
|
||||||
|
uint32_t tileX = x % 16;
|
||||||
|
uint32_t blockY = y / 16;
|
||||||
|
uint32_t tileY = y % 16;
|
||||||
|
MapExtras::Block * b = mc.BlockAt(cursor/16);
|
||||||
|
if(b && b->is_valid())
|
||||||
|
{
|
||||||
|
auto &block = *b->getRaw();
|
||||||
|
df::tile_occupancy &occ = block.occupancy[tileX][tileY];
|
||||||
|
if (!occ.bits.item)
|
||||||
|
free++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isFull() { return free == 0; }
|
||||||
|
|
||||||
|
bool canHold(df::item *i)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool inStockpile(df::item *i)
|
||||||
|
{
|
||||||
|
df::item *container = Items::getContainer(i);
|
||||||
|
if (container)
|
||||||
|
return inStockpile(container);
|
||||||
|
|
||||||
|
if (i->pos.z != z) return false;
|
||||||
|
if (i->pos.x < x1 || i->pos.x >= x2 ||
|
||||||
|
i->pos.y < y1 || i->pos.y >= y2) return false;
|
||||||
|
int e = (i->pos.x - x1) + (i->pos.y - y1) * sp->room.width;
|
||||||
|
return sp->room.extents[e] == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getId() { return sp->id; }
|
||||||
|
};
|
||||||
|
|
||||||
|
static command_result stockcheck(color_ostream &out, vector <string> & parameters)
|
||||||
|
{
|
||||||
|
CoreSuspender suspend;
|
||||||
|
|
||||||
|
std::vector<StockpileInfo*> stockpiles;
|
||||||
|
|
||||||
|
for (int i = 0; i < world->buildings.all.size(); ++i)
|
||||||
|
{
|
||||||
|
df::building *build = world->buildings.all[i];
|
||||||
|
auto type = build->getType();
|
||||||
|
if (df::enums::building_type::Stockpile == type)
|
||||||
|
{
|
||||||
|
building_stockpilest *sp = virtual_cast<building_stockpilest>(build);
|
||||||
|
StockpileInfo *spi = new StockpileInfo(sp);
|
||||||
|
stockpiles.push_back(spi);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<df::item*> &items = world->items.other[items_other_id::ANY_FREE];
|
||||||
|
|
||||||
|
// Precompute a bitmask with the bad flags
|
||||||
|
df::item_flags bad_flags;
|
||||||
|
bad_flags.whole = 0;
|
||||||
|
|
||||||
|
#define F(x) bad_flags.bits.x = true;
|
||||||
|
F(dump); F(forbid); F(garbage_collect);
|
||||||
|
F(hostile); F(on_fire); F(rotten); F(trader);
|
||||||
|
F(in_building); F(construction); F(artifact1);
|
||||||
|
F(spider_web); F(owned); F(in_job);
|
||||||
|
#undef F
|
||||||
|
|
||||||
|
for (size_t i = 0; i < items.size(); i++)
|
||||||
|
{
|
||||||
|
df::item *item = items[i];
|
||||||
|
if (item->flags.whole & bad_flags.whole)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// we really only care about MEAT, FISH, FISH_RAW, PLANT, CHEESE, FOOD, and EGG
|
||||||
|
|
||||||
|
df::item_type typ = item->getType();
|
||||||
|
if (typ != df::enums::item_type::MEAT &&
|
||||||
|
typ != df::enums::item_type::FISH &&
|
||||||
|
typ != df::enums::item_type::FISH_RAW &&
|
||||||
|
typ != df::enums::item_type::PLANT &&
|
||||||
|
typ != df::enums::item_type::CHEESE &&
|
||||||
|
typ != df::enums::item_type::FOOD &&
|
||||||
|
typ != df::enums::item_type::EGG)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
df::item *container = 0;
|
||||||
|
df::unit *holder = 0;
|
||||||
|
df::building *building = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < item->itemrefs.size(); i++)
|
||||||
|
{
|
||||||
|
df::general_ref *ref = item->itemrefs[i];
|
||||||
|
|
||||||
|
switch (ref->getType())
|
||||||
|
{
|
||||||
|
case general_ref_type::CONTAINED_IN_ITEM:
|
||||||
|
container = ref->getItem();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case general_ref_type::UNIT_HOLDER:
|
||||||
|
holder = ref->getUnit();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case general_ref_type::BUILDING_HOLDER:
|
||||||
|
building = ref->getBuilding();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
df::item *nextcontainer = container;
|
||||||
|
df::item *lastcontainer = 0;
|
||||||
|
|
||||||
|
while(nextcontainer) {
|
||||||
|
df::item *thiscontainer = nextcontainer;
|
||||||
|
nextcontainer = 0;
|
||||||
|
for (size_t i = 0; i < thiscontainer->itemrefs.size(); i++)
|
||||||
|
{
|
||||||
|
df::general_ref *ref = thiscontainer->itemrefs[i];
|
||||||
|
|
||||||
|
switch (ref->getType())
|
||||||
|
{
|
||||||
|
case general_ref_type::CONTAINED_IN_ITEM:
|
||||||
|
lastcontainer = nextcontainer = ref->getItem();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case general_ref_type::UNIT_HOLDER:
|
||||||
|
holder = ref->getUnit();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case general_ref_type::BUILDING_HOLDER:
|
||||||
|
building = ref->getBuilding();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (holder)
|
||||||
|
continue; // carried items do not rot as far as i know
|
||||||
|
|
||||||
|
if (building) {
|
||||||
|
df::building_type btype = building->getType();
|
||||||
|
if (btype == df::enums::building_type::TradeDepot ||
|
||||||
|
btype == df::enums::building_type::Wagon)
|
||||||
|
continue; // items in trade depot or the embark wagon do not rot
|
||||||
|
|
||||||
|
if (typ == df::enums::item_type::EGG && btype ==df::enums::building_type::NestBox)
|
||||||
|
continue; // eggs in nest box do not rot
|
||||||
|
}
|
||||||
|
|
||||||
|
int canHoldCount = 0;
|
||||||
|
StockpileInfo *current = 0;
|
||||||
|
|
||||||
|
for (int idx = 0; idx < stockpiles.size(); idx++)
|
||||||
|
{
|
||||||
|
StockpileInfo *spi = stockpiles[idx];
|
||||||
|
if (spi->canHold(item)) canHoldCount++;
|
||||||
|
if (spi->inStockpile(item)) current=spi;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::string description;
|
||||||
|
item->getItemDescription(&description, 0);
|
||||||
|
out << " * " << description;
|
||||||
|
|
||||||
|
if (container) {
|
||||||
|
std::string containerDescription;
|
||||||
|
container->getItemDescription(&containerDescription, 0);
|
||||||
|
out << ", in container " << containerDescription;
|
||||||
|
if (lastcontainer) {
|
||||||
|
std::string lastcontainerDescription;
|
||||||
|
lastcontainer->getItemDescription(&lastcontainerDescription, 0);
|
||||||
|
out << ", in container " << lastcontainerDescription;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (holder) {
|
||||||
|
out << ", carried";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (building) {
|
||||||
|
out << ", in building " << building->id << " (type=" << building->getType() << ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
out << ", flags=" << std::hex << item->flags.whole << std::dec;
|
||||||
|
out << endl;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,103 @@
|
|||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <climits>
|
||||||
|
#include <vector>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <string>
|
||||||
|
#include <sstream>
|
||||||
|
#include <ctime>
|
||||||
|
#include <cstdio>
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
#include "Core.h"
|
||||||
|
#include "Console.h"
|
||||||
|
#include "Export.h"
|
||||||
|
#include "PluginManager.h"
|
||||||
|
#include "modules/Units.h"
|
||||||
|
#include "modules/Maps.h"
|
||||||
|
#include "modules/Gui.h"
|
||||||
|
#include "modules/World.h"
|
||||||
|
#include "MiscUtils.h"
|
||||||
|
|
||||||
|
#include <df/ui.h>
|
||||||
|
#include "df/world.h"
|
||||||
|
#include "df/world_raws.h"
|
||||||
|
#include "df/building_def.h"
|
||||||
|
#include "df/unit_inventory_item.h"
|
||||||
|
#include <df/creature_raw.h>
|
||||||
|
#include <df/caste_raw.h>
|
||||||
|
|
||||||
|
using std::vector;
|
||||||
|
using std::string;
|
||||||
|
using namespace DFHack;
|
||||||
|
using namespace df::enums;
|
||||||
|
using df::global::world;
|
||||||
|
using df::global::cursor;
|
||||||
|
using df::global::ui;
|
||||||
|
|
||||||
|
using namespace DFHack::Gui;
|
||||||
|
|
||||||
|
command_result df_stripcaged(color_ostream &out, vector <string> & parameters);
|
||||||
|
|
||||||
|
DFHACK_PLUGIN("stripcaged");
|
||||||
|
|
||||||
|
// check if contained in item (e.g. animals in cages)
|
||||||
|
bool isContainedInItem(df::unit* unit)
|
||||||
|
{
|
||||||
|
bool contained = false;
|
||||||
|
for (size_t r=0; r < unit->refs.size(); r++)
|
||||||
|
{
|
||||||
|
df::general_ref * ref = unit->refs[r];
|
||||||
|
auto rtype = ref->getType();
|
||||||
|
if(rtype == df::general_ref_type::CONTAINED_IN_ITEM)
|
||||||
|
{
|
||||||
|
contained = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return contained;
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||||
|
{
|
||||||
|
commands.push_back(PluginCommand(
|
||||||
|
"stripcaged", "strip caged units of all items",
|
||||||
|
df_stripcaged, false,
|
||||||
|
"Clears forbid and sets dump for the inventories of all caged units."
|
||||||
|
));
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||||
|
{
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
command_result df_stripcaged(color_ostream &out, vector <string> & parameters)
|
||||||
|
{
|
||||||
|
CoreSuspender suspend;
|
||||||
|
|
||||||
|
size_t count = 0;
|
||||||
|
for (size_t i=0; i < world->units.all.size(); i++)
|
||||||
|
{
|
||||||
|
df::unit* unit = world->units.all[i];
|
||||||
|
if (isContainedInItem(unit))
|
||||||
|
{
|
||||||
|
for (size_t j=0; j < unit->inventory.size(); j++)
|
||||||
|
{
|
||||||
|
df::unit_inventory_item* uii = unit->inventory[j];
|
||||||
|
if (uii->item)
|
||||||
|
{
|
||||||
|
uii->item->flags.bits.forbid = 0;
|
||||||
|
uii->item->flags.bits.dump = 1;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out << count << " items marked for dumping" << endl;
|
||||||
|
|
||||||
|
return CR_OK;
|
||||||
|
}
|
@ -0,0 +1,641 @@
|
|||||||
|
// forceequip plugin
|
||||||
|
// Moves local items from the ground into a unit's inventory
|
||||||
|
#include <iostream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <sstream>
|
||||||
|
#include <climits>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <set>
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
#include "Core.h"
|
||||||
|
#include "Console.h"
|
||||||
|
#include "Export.h"
|
||||||
|
#include "PluginManager.h"
|
||||||
|
#include "modules/Maps.h"
|
||||||
|
#include "modules/Gui.h"
|
||||||
|
#include "modules/Items.h"
|
||||||
|
#include "modules/Materials.h"
|
||||||
|
#include "modules/MapCache.h"
|
||||||
|
#include "DataDefs.h"
|
||||||
|
#include "df/item.h"
|
||||||
|
#include "df/itemdef.h"
|
||||||
|
#include "df/world.h"
|
||||||
|
#include "df/general_ref.h"
|
||||||
|
#include "df/unit.h"
|
||||||
|
#include "df/body_part_raw.h"
|
||||||
|
#include "MiscUtils.h"
|
||||||
|
#include "df/unit_inventory_item.h"
|
||||||
|
#include "df/body_part_raw_flags.h"
|
||||||
|
#include "df/creature_raw.h"
|
||||||
|
#include "df/caste_raw.h"
|
||||||
|
#include "df/body_detail_plan.h"
|
||||||
|
#include "df/body_template.h"
|
||||||
|
#include "df/body_part_template.h"
|
||||||
|
#include "df/unit_soul.h"
|
||||||
|
#include "df/unit_skill.h"
|
||||||
|
#include "df/general_ref.h"
|
||||||
|
#include "df/caste_raw.h"
|
||||||
|
|
||||||
|
#include "DFHack.h"
|
||||||
|
|
||||||
|
using namespace DFHack;
|
||||||
|
using namespace df::enums;
|
||||||
|
|
||||||
|
using MapExtras::Block;
|
||||||
|
using MapExtras::MapCache;
|
||||||
|
using df::global::world;
|
||||||
|
|
||||||
|
const int const_GloveRightHandedness = 1;
|
||||||
|
const int const_GloveLeftHandedness = 2;
|
||||||
|
|
||||||
|
DFHACK_PLUGIN("forceequip");
|
||||||
|
|
||||||
|
command_result df_forceequip(color_ostream &out, vector <string> & parameters);
|
||||||
|
|
||||||
|
const string forceequip_help =
|
||||||
|
"ForceEquip moves local items into a unit's inventory. It is typically\n"
|
||||||
|
"used to equip specific clothing/armor items onto a dwarf, but can also\n"
|
||||||
|
"be used to put armor onto a war animal or to add unusual items (such\n"
|
||||||
|
"as crowns) to any unit.\n"
|
||||||
|
"This plugin can process multiple items in a single call, but will only\n"
|
||||||
|
"work with a single unit (the first one it finds under the cursor).\n"
|
||||||
|
"In order to minimize confusion, it is recommended that you use\n"
|
||||||
|
"forceequip only when you have a unit standing alone atop a pile of\n"
|
||||||
|
"gear that you would like it to wear. Items which are stored in bins\n"
|
||||||
|
"or other containers (e.g. chests, armor racks) may also work, but\n"
|
||||||
|
"piling items on the floor (via a garbage dump activity zone, of the\n"
|
||||||
|
"DFHack autodump command) is the most reliable way to do it.\n"
|
||||||
|
"The plugin will ignore any items that are forbidden. Hence, you\n"
|
||||||
|
"can setup a large pile of surplus gear, walk a unit onto it (or\n"
|
||||||
|
"pasture an animal on it), unforbid a few items and run forceequip.\n"
|
||||||
|
"The (forbidden) majority of your gear will remain in-place, ready\n"
|
||||||
|
"for the next passerby."
|
||||||
|
"\n"
|
||||||
|
"As mentioned above, this plugin can be used to equip items onto\n"
|
||||||
|
"units (such as animals) which cannot normally equip gear. There's\n"
|
||||||
|
"an important caveat here - such creatures will automatically drop\n"
|
||||||
|
"inappropriate gear almost immediately (within 10 game ticks).\n"
|
||||||
|
"If you want them to retain their equipment, you must forbid it\n"
|
||||||
|
"AFTER using forceequip to get it into their inventory.\n"
|
||||||
|
"This technique can also be used to clothe dwarven infants, but\n"
|
||||||
|
"only if you're able to separate them from their mothers.\n"
|
||||||
|
"\n"
|
||||||
|
"By default, the forceequip plugin will attempt to avoid\n"
|
||||||
|
"conflicts and outright cheating. For instance, it will skip\n"
|
||||||
|
"any item which is flagged for use in a job, and will not\n"
|
||||||
|
"equip more than one piece of clothing/armor onto any given\n"
|
||||||
|
"body part. These restrictions can be overridden via command\n"
|
||||||
|
"switches (see examples below) but doing so puts you at greater\n"
|
||||||
|
"risk of unexpected consequences. For instance, a dwarf who\n"
|
||||||
|
"is wearing three breastplates will not be able to move very\n"
|
||||||
|
"quickly.\n"
|
||||||
|
"\n"
|
||||||
|
"Items equipped by this plugin DO NOT become owned by the\n"
|
||||||
|
"recipient. Adult dwarves are free to adjust their own\n"
|
||||||
|
"wardrobe, and may promptly decide to doff your gear in\n"
|
||||||
|
"favour of their owned items. Animals, as described above,\n"
|
||||||
|
"will tend to discard ALL clothing immediately unless it is\n"
|
||||||
|
"manually forbidden. Armor items seem to be an exception;\n"
|
||||||
|
"an animal will tend to retain an equipped suit of mail\n"
|
||||||
|
"even if you neglect to Forbid it.\n"
|
||||||
|
"\n"
|
||||||
|
"Please note that armored animals are quite vulnerable to ranged\n"
|
||||||
|
"attacks. Unlike dwarves, animals cannot block, dodge, or deflect\n"
|
||||||
|
"arrows, and they are slowed by the weight of their armor.\n"
|
||||||
|
"\n"
|
||||||
|
"This plugin currently does not support weapons.\n"
|
||||||
|
"\n"
|
||||||
|
"Options:\n"
|
||||||
|
" here, h - process the unit and item(s) under the cursor.\n"
|
||||||
|
" - This option is enabled by default since the plugin\n"
|
||||||
|
" - does not currently support remote equpping.\n"
|
||||||
|
" ignore, i - bypasses the usual item eligibility checks (such as\n"
|
||||||
|
" - \"Never equip gear belonging to another dwarf\" and\n"
|
||||||
|
" - \"Nobody is allowed to equip a Hive\".)\n"
|
||||||
|
" multi, m - bypasses the 1-item-per-bodypart limit, allowing\n"
|
||||||
|
" - the unit to receive an unlimited amount of gear.\n"
|
||||||
|
" - Can be used legitimately (e.g. mitten + gauntlet)\n"
|
||||||
|
" - or for cheating (e.g. twelve breastplates).\n"
|
||||||
|
" m2, m3, m4 - alters the 1-item-per-bodypart limit, allowing\n"
|
||||||
|
" - each part to receive 2, 3, or 4 pieces of gear.\n"
|
||||||
|
" selected, s - rather than processing all items piled at a unit's\n"
|
||||||
|
" - feet, process only the one item currently selected.\n"
|
||||||
|
" bodypart, bp - must be followed by a bodypart code (e.g. LH).\n"
|
||||||
|
" - Instructs the plugin to equip all available items\n"
|
||||||
|
" - onto this body part only. Typically used in\n"
|
||||||
|
" - conjunction with the f switch (to over-armor\n"
|
||||||
|
" - a particular bodypart) or the i switch (to equip\n"
|
||||||
|
" - an unusual item onto a specific slot).\n"
|
||||||
|
" verbose, v - provides detailed narration and error messages.\n"
|
||||||
|
" - Can be helpful in resolving failures; not needed\n"
|
||||||
|
" - for casual use.\n"
|
||||||
|
"\n"
|
||||||
|
"Examples:\n"
|
||||||
|
" forceequip\n"
|
||||||
|
" attempts to equip all of the items under the cursor onto the unit\n"
|
||||||
|
" under the cursor. Uses only clothing/armor items; ignores all\n"
|
||||||
|
" other types. Equips a maximum of 1 item onto each bodypart,\n"
|
||||||
|
" and equips only \"appropriate\" items in each slot (e.g. glove\n"
|
||||||
|
" --> hand). Bypasses any item which might cause a conflict,\n"
|
||||||
|
" such as a Boot belonging to a different dwarf.\n"
|
||||||
|
" forceequip bp LH\n"
|
||||||
|
" attempts to equip all local items onto the left hand of the local\n"
|
||||||
|
" unit. If the hand is already equipped then nothing will happen,\n"
|
||||||
|
" and if it is not equipped then only one appropriate item (e.g. \n"
|
||||||
|
" a single mitten or gauntlet) will be equipped. This command can\n"
|
||||||
|
" be useful if you don't want to selectively forbid individual items\n"
|
||||||
|
" and simply want the unit to equip, say, a Helmet while leaving\n"
|
||||||
|
" the rest of the pile alone.\n"
|
||||||
|
" forceequip m bp LH\n"
|
||||||
|
" as above, but will equip ALL appropriate items onto the unit's\n"
|
||||||
|
" left hand. After running this command, it might end up wearing\n"
|
||||||
|
" a dozen left-handed mittens. Use with caution, and remember\n"
|
||||||
|
" that dwarves will tend to drop supernumary items ASAP.\n"
|
||||||
|
" forceequip m\n"
|
||||||
|
" as above, but will equip ALL appropriate items onto any\n"
|
||||||
|
" appropriate bodypart. Tends to put several boots onto the right\n"
|
||||||
|
" foot while leaving the left foot bare.\n"
|
||||||
|
" forceequip m2\n"
|
||||||
|
" as above, but will equip up to two appropriate items onto each\n"
|
||||||
|
" bodypart. Helps to balance footwear, but doesn't ensure proper\n"
|
||||||
|
" placement (e.g. left foot gets two socks, right foot gets two\n"
|
||||||
|
" shoes). For best results, use \"selected bp LH\" and\n"
|
||||||
|
" \"selected bp RH\" instead.\n"
|
||||||
|
" forceequip i\n"
|
||||||
|
" performs the standard \"equip appropriate items onto appropriate\n"
|
||||||
|
" bodyparts\" logic, but also includes items that would normally\n"
|
||||||
|
" be considered ineligible (such as a sock which is owned by\n"
|
||||||
|
" a different dwarf).\n"
|
||||||
|
" forceequip bp NECK\n"
|
||||||
|
" attempts to equip any appropriate gear onto the Neck of the\n"
|
||||||
|
" local unit. Since the plugin believes that no items are actually\n"
|
||||||
|
" appropriate for the Neck slot, this command does nothing.\n"
|
||||||
|
" forceequip i bp NECK\n"
|
||||||
|
" attempts to equip items from the local pile onto the Neck\n"
|
||||||
|
" of the local unit. Ignores appropriateness restrictions.\n"
|
||||||
|
" If there's a millstone or an albatross carcass sitting on\n"
|
||||||
|
" the same square as the targeted unit, then there's a good\n"
|
||||||
|
" chance that it will end up around his neck. For precise\n"
|
||||||
|
" control, remember that you can selectively forbid some of\n"
|
||||||
|
" the items that are piled on the ground.\n"
|
||||||
|
" forceequip i m bp NECK\n"
|
||||||
|
" as above, but equips an unlimited number of items onto the\n"
|
||||||
|
" targeted bodypart. Effectively, all unforbidden items\n"
|
||||||
|
" (including helms, millstones, boulders, etc) will be\n"
|
||||||
|
" moved from the local pile and placed in the dwarf's\n"
|
||||||
|
" inventory (specifically, on his neck). When used with\n"
|
||||||
|
" a large pile of goods, this will leave the dwarf heavily\n"
|
||||||
|
" encumbered and very slow to move.\n"
|
||||||
|
" forceequip s\n"
|
||||||
|
" requires that a single item be selected using the k menu.\n"
|
||||||
|
" This item must occupy the same square as the target unit,\n"
|
||||||
|
" and must be unforbidden. Attempts to equip this single\n"
|
||||||
|
" item onto an appropriate slot in the unit's inventory.\n"
|
||||||
|
" Can serve as a quicker alternative to the selective-\n"
|
||||||
|
" unforbidding approach described above.\n"
|
||||||
|
" forceequip s m i bp HD\n"
|
||||||
|
" equips the selected item onto the unit's head. Ignores\n"
|
||||||
|
" all possible restrictions and conflicts. If you know\n"
|
||||||
|
" exactly what you want to equip, and exactly where you\n"
|
||||||
|
" want it to go, then this is the most straightforward\n"
|
||||||
|
" and reliable option.\n"
|
||||||
|
" forceequip v bp QQQ\n"
|
||||||
|
" guaranteed to fail (and accomplish nothing) because\n"
|
||||||
|
" there are no bodyparts called QQQ. However, since the\n"
|
||||||
|
" verbose switch is used, the resulting error messages\n"
|
||||||
|
" will list every bodypart that the unit DOES possess.\n"
|
||||||
|
" May be useful if you're unfamiliar with the BP codes\n"
|
||||||
|
" used by Dwarf Fortress, or if you're experimenting\n"
|
||||||
|
" with an exotic creature.\n"
|
||||||
|
"\n"
|
||||||
|
;
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_init ( color_ostream &out, vector <PluginCommand> &commands)
|
||||||
|
{
|
||||||
|
commands.push_back(PluginCommand(
|
||||||
|
"forceequip", "Moves local items from the ground into a unit's inventory",
|
||||||
|
df_forceequip, false,
|
||||||
|
forceequip_help.c_str()
|
||||||
|
));
|
||||||
|
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||||
|
{
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *unit, df::body_part_raw * targetBodyPart, bool ignoreRestrictions, int multiEquipLimit, bool verbose)
|
||||||
|
{
|
||||||
|
// Step 1: Check for anti-requisite conditions
|
||||||
|
df::unit * itemOwner = Items::getOwner(item);
|
||||||
|
if (ignoreRestrictions)
|
||||||
|
{
|
||||||
|
// If the ignoreRestrictions cmdline switch was specified, then skip all of the normal preventative rules
|
||||||
|
if (verbose) { Core::print("Skipping integrity checks...\n"); }
|
||||||
|
}
|
||||||
|
else if(!item->isClothing() && !item->isArmorNotClothing())
|
||||||
|
{
|
||||||
|
if (verbose) { Core::printerr("Item %d is not clothing or armor; it cannot be equipped. Please choose a different item (or use the Ignore option if you really want to equip an inappropriate item).\n", item->id); }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (item->getType() != df::enums::item_type::GLOVES &&
|
||||||
|
item->getType() != df::enums::item_type::HELM &&
|
||||||
|
item->getType() != df::enums::item_type::ARMOR &&
|
||||||
|
item->getType() != df::enums::item_type::PANTS &&
|
||||||
|
item->getType() != df::enums::item_type::SHOES &&
|
||||||
|
!targetBodyPart)
|
||||||
|
{
|
||||||
|
if (verbose) { Core::printerr("Item %d is of an unrecognized type; it cannot be equipped (because the module wouldn't know where to put it).\n", item->id); }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (itemOwner && itemOwner->id != unit->id)
|
||||||
|
{
|
||||||
|
if (verbose) { Core::printerr("Item %d is owned by someone else. Equipping it on this unit is not recommended. Please use DFHack's Confiscate plugin, choose a different item, or use the Ignore option to proceed in spite of this warning.\n", item->id); }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (item->flags.bits.in_inventory)
|
||||||
|
{
|
||||||
|
if (verbose) { Core::printerr("Item %d is already in a unit's inventory. Direct inventory transfers are not recommended; please move the item to the ground first (or use the Ignore option).\n", item->id); }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (item->flags.bits.in_job)
|
||||||
|
{
|
||||||
|
if (verbose) { Core::printerr("Item %d is reserved for use in a queued job. Equipping it is not recommended, as this might interfere with the completion of vital jobs. Use the Ignore option to ignore this warning.\n", item->id); }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ASSERT: anti-requisite conditions have been satisfied (or disregarded)
|
||||||
|
|
||||||
|
|
||||||
|
// Step 2: Try to find a bodypart which is eligible to receive equipment AND which is appropriate for the specified item
|
||||||
|
df::body_part_raw * confirmedBodyPart = NULL;
|
||||||
|
int bpIndex;
|
||||||
|
for(bpIndex = 0; bpIndex < unit->body.body_plan->body_parts.size(); bpIndex++)
|
||||||
|
{
|
||||||
|
df::body_part_raw * currPart = unit->body.body_plan->body_parts[bpIndex];
|
||||||
|
|
||||||
|
// Short-circuit the search process if a BP was specified in the function call
|
||||||
|
// Note: this causes a bit of inefficient busy-looping, but the search space is tiny (<100) and we NEED to get the correct bpIndex value in order to perform inventory manipulations
|
||||||
|
if (!targetBodyPart)
|
||||||
|
{
|
||||||
|
// The function call did not specify any particular body part; proceed with normal iteration and evaluation of BP eligibility
|
||||||
|
}
|
||||||
|
else if (currPart == targetBodyPart)
|
||||||
|
{
|
||||||
|
// A specific body part was included in the function call, and we've found it; proceed with the normal BP evaluation (suitability, emptiness, etc)
|
||||||
|
}
|
||||||
|
else if (bpIndex < unit->body.body_plan->body_parts.size())
|
||||||
|
{
|
||||||
|
// The current body part is not the one that was specified in the function call, but we can keep searching
|
||||||
|
if (verbose) { Core::printerr("Found bodypart %s; not a match; continuing search.\n", currPart->token.c_str()); }
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// The specified body part has not been found, and we've reached the end of the list. Report failure.
|
||||||
|
if (verbose) { Core::printerr("The specified body part (%s) does not belong to the chosen unit. Please double-check to ensure that your spelling is correct, and that you have not chosen a dismembered bodypart.\n"); }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verbose) { Core::print("Inspecting bodypart %s.\n", currPart->token.c_str()); }
|
||||||
|
|
||||||
|
// Inspect the current bodypart
|
||||||
|
if (item->getType() == df::enums::item_type::GLOVES && currPart->flags.is_set(df::body_part_raw_flags::GRASP) &&
|
||||||
|
((item->getGloveHandedness() == const_GloveLeftHandedness && currPart->flags.is_set(df::body_part_raw_flags::LEFT)) ||
|
||||||
|
(item->getGloveHandedness() == const_GloveRightHandedness && currPart->flags.is_set(df::body_part_raw_flags::RIGHT))))
|
||||||
|
{
|
||||||
|
if (verbose) { Core::print("Hand found (%s)...", currPart->token.c_str()); }
|
||||||
|
}
|
||||||
|
else if (item->getType() == df::enums::item_type::HELM && currPart->flags.is_set(df::body_part_raw_flags::HEAD))
|
||||||
|
{
|
||||||
|
if (verbose) { Core::print("Head found (%s)...", currPart->token.c_str()); }
|
||||||
|
}
|
||||||
|
else if (item->getType() == df::enums::item_type::ARMOR && currPart->flags.is_set(df::body_part_raw_flags::UPPERBODY))
|
||||||
|
{
|
||||||
|
if (verbose) { Core::print("Upper body found (%s)...", currPart->token.c_str()); }
|
||||||
|
}
|
||||||
|
else if (item->getType() == df::enums::item_type::PANTS && currPart->flags.is_set(df::body_part_raw_flags::LOWERBODY))
|
||||||
|
{
|
||||||
|
if (verbose) { Core::print("Lower body found (%s)...", currPart->token.c_str()); }
|
||||||
|
}
|
||||||
|
else if (item->getType() == df::enums::item_type::SHOES && currPart->flags.is_set(df::body_part_raw_flags::STANCE))
|
||||||
|
{
|
||||||
|
if (verbose) { Core::print("Foot found (%s)...", currPart->token.c_str()); }
|
||||||
|
}
|
||||||
|
else if (targetBodyPart && ignoreRestrictions)
|
||||||
|
{
|
||||||
|
// The BP in question would normally be considered ineligible for equipment. But since it was deliberately specified by the user, we'll proceed anyways.
|
||||||
|
if (verbose) { Core::print("Non-standard bodypart found (%s)...", targetBodyPart->token.c_str()); }
|
||||||
|
}
|
||||||
|
else if (targetBodyPart)
|
||||||
|
{
|
||||||
|
// The BP in question is not eligible for equipment and the ignore flag was not specified. Report failure.
|
||||||
|
if (verbose) { Core::printerr("Non-standard bodypart found, but it is ineligible for standard equipment. Use the Ignore flag to override this warning.\n"); }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (verbose) { Core::print("Skipping ineligible bodypart.\n"); }
|
||||||
|
// This body part is not eligible for the equipment in question; skip it
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ASSERT: The current body part is able to support the specified equipment (or the test has been overridden). Check whether it is currently empty/available.
|
||||||
|
|
||||||
|
if (multiEquipLimit == INT_MAX)
|
||||||
|
{
|
||||||
|
// Note: this loop/check is skipped if the MultiEquip option is specified; we'll simply add the item to the bodyPart even if it's already holding a dozen gloves, shoes, and millstones (or whatever)
|
||||||
|
if (verbose) { Core::print(" inventory checking skipped..."); }
|
||||||
|
confirmedBodyPart = currPart;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
confirmedBodyPart = currPart; // Assume that the bodypart is valid; we'll invalidate it if we detect too many collisions while looping
|
||||||
|
int collisions = 0;
|
||||||
|
for (int inventoryID=0; inventoryID < unit->inventory.size(); inventoryID++)
|
||||||
|
{
|
||||||
|
df::unit_inventory_item * currInvItem = unit->inventory[inventoryID];
|
||||||
|
if (currInvItem->body_part_id == bpIndex)
|
||||||
|
{
|
||||||
|
// Collision detected; have we reached the limit?
|
||||||
|
if (++collisions >= multiEquipLimit)
|
||||||
|
{
|
||||||
|
if (verbose) { Core::printerr(" but it already carries %d piece(s) of equipment. Either remove the existing equipment or use the Multi option.\n", multiEquipLimit); }
|
||||||
|
confirmedBodyPart = NULL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (confirmedBodyPart)
|
||||||
|
{
|
||||||
|
// Match found; no need to examine any other BPs
|
||||||
|
if (verbose) { Core::print(" eligibility confirmed..."); }
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (!targetBodyPart)
|
||||||
|
{
|
||||||
|
// This body part is not eligible to receive the specified equipment; return to the loop and check the next BP
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// A specific body part was designated in the function call, but it was found to be ineligible.
|
||||||
|
// Don't return to the BP loop; just fall-through to the failure-reporting code a few lines below.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!confirmedBodyPart) {
|
||||||
|
// No matching body parts found; report failure
|
||||||
|
if (verbose) { Core::printerr("\nThe item could not be equipped because the relevant body part(s) of the unit are missing or already occupied. Try again with the Multi option if you're like to over-equip a body part, or choose a different unit-item combination (e.g. stop trying to put shoes on a trout).\n" ); }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Items::moveToInventory(mc, item, unit, df::unit_inventory_item::Worn, bpIndex))
|
||||||
|
{
|
||||||
|
if (verbose) { Core::printerr("\nEquipping failed - failed to retrieve item from its current location/container/inventory. Please move it to the ground and try again.\n"); }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verbose) { Core::print(" Success!\n"); }
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
command_result df_forceequip(color_ostream &out, vector <string> & parameters)
|
||||||
|
{
|
||||||
|
// The "here" option is hardcoded to true, because the plugin currently doesn't support
|
||||||
|
// equip-at-a-distance (e.g. grab items within 10 squares of the targeted unit)
|
||||||
|
bool here = true;
|
||||||
|
// For balance (anti-cheating) reasons, the plugin applies a limit on the number of
|
||||||
|
// item that can be equipped on any bodypart. This limit defaults to 1 but can be
|
||||||
|
// overridden with cmdline switches.
|
||||||
|
int multiEquipLimit = 1;
|
||||||
|
// The plugin applies several pre-checks in order to reduce the risk of conflict
|
||||||
|
// and unintended side-effects. Most of these checks can be disabled via cmdline
|
||||||
|
bool ignore = false;
|
||||||
|
// By default, the plugin uses all gear piled on the selected square. Optionally,
|
||||||
|
// it can target only a single item (selected on the k menu) instead
|
||||||
|
bool selected = false;
|
||||||
|
// Most of the plugin's text output is suppressed by default. It can be enabled
|
||||||
|
// to provide insight into errors, and/or for debugging purposes.
|
||||||
|
bool verbose = false;
|
||||||
|
// By default, the plugin will mate each item to an appropriate bodypart. This
|
||||||
|
// behaviour can be skipped if the user specifies a particular BP in the cmdline input.
|
||||||
|
std::string targetBodyPartCode;
|
||||||
|
|
||||||
|
// Parse the input
|
||||||
|
for (size_t i = 0; i < parameters.size(); i++)
|
||||||
|
{
|
||||||
|
string & p = parameters[i];
|
||||||
|
|
||||||
|
if (p == "help" || p == "?" || p == "h" || p == "/?" || p == "info" || p == "man")
|
||||||
|
{
|
||||||
|
out << forceequip_help << endl;
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
else if (p == "here" || p == "h")
|
||||||
|
{
|
||||||
|
here = true;
|
||||||
|
}
|
||||||
|
else if (p == "ignore" || p == "i")
|
||||||
|
{
|
||||||
|
ignore = true;
|
||||||
|
}
|
||||||
|
else if (p == "multi" || p == "m")
|
||||||
|
{
|
||||||
|
multiEquipLimit = INT_MAX;
|
||||||
|
}
|
||||||
|
else if (p == "m2")
|
||||||
|
{
|
||||||
|
multiEquipLimit = 2;
|
||||||
|
}
|
||||||
|
else if (p == "m3")
|
||||||
|
{
|
||||||
|
multiEquipLimit = 3;
|
||||||
|
}
|
||||||
|
else if (p == "m4")
|
||||||
|
{
|
||||||
|
multiEquipLimit = 4;
|
||||||
|
}
|
||||||
|
else if (p == "selected" || p == "s")
|
||||||
|
{
|
||||||
|
selected = true;
|
||||||
|
}
|
||||||
|
else if (p == "verbose" || p == "v")
|
||||||
|
{
|
||||||
|
verbose = true;
|
||||||
|
}
|
||||||
|
else if (p == "bodypart" || p == "bp" )
|
||||||
|
{
|
||||||
|
// must be followed by bodypart code (e.g. NECK)
|
||||||
|
if(i == parameters.size()-1 || parameters[i+1].size() == 0)
|
||||||
|
{
|
||||||
|
out.printerr("The bp switch must be followed by a bodypart code!\n");
|
||||||
|
return CR_FAILURE;
|
||||||
|
}
|
||||||
|
targetBodyPartCode = parameters[i+1];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
out << p << ": Unknown command! Type \"forceequip help\" for assistance." << endl;
|
||||||
|
return CR_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the map information is available (e.g. a game is actually in-progress)
|
||||||
|
if (!Maps::IsValid())
|
||||||
|
{
|
||||||
|
out.printerr("Map is not available!\n");
|
||||||
|
return CR_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup the cursor position
|
||||||
|
int cx, cy, cz;
|
||||||
|
DFCoord pos_cursor;
|
||||||
|
|
||||||
|
// needs a cursor
|
||||||
|
if (!Gui::getCursorCoords(cx,cy,cz))
|
||||||
|
{
|
||||||
|
out.printerr("Cursor position not found. Please enable the cursor.\n");
|
||||||
|
return CR_FAILURE;
|
||||||
|
}
|
||||||
|
pos_cursor = DFCoord(cx,cy,cz);
|
||||||
|
|
||||||
|
// Iterate over all units, process the first one whose pos == pos_cursor
|
||||||
|
df::unit * targetUnit;
|
||||||
|
size_t numUnits = world->units.all.size();
|
||||||
|
for(size_t i=0; i< numUnits; i++)
|
||||||
|
{
|
||||||
|
targetUnit = world->units.all[i]; // tentatively assume that we have a match; then verify
|
||||||
|
DFCoord pos_unit(targetUnit->pos.x, targetUnit->pos.y, targetUnit->pos.z);
|
||||||
|
|
||||||
|
if (pos_unit == pos_cursor)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (i + 1 == numUnits)
|
||||||
|
{
|
||||||
|
out.printerr("No unit found at cursor!\n");
|
||||||
|
return CR_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert: unit found.
|
||||||
|
|
||||||
|
// If a specific bodypart was included in the command arguments, then search for it now
|
||||||
|
df::body_part_raw * targetBodyPart = NULL;
|
||||||
|
if (targetBodyPartCode.size() > 0) {
|
||||||
|
for (int bpIndex = 0; bpIndex < targetUnit->body.body_plan->body_parts.size(); bpIndex ++)
|
||||||
|
{
|
||||||
|
// Tentatively assume that the part is a match
|
||||||
|
targetBodyPart = targetUnit->body.body_plan->body_parts.at(bpIndex);
|
||||||
|
if (targetBodyPart->token.compare(targetBodyPartCode) == 0)
|
||||||
|
{
|
||||||
|
// It is indeed a match; exit the loop (while leaving the variable populated)
|
||||||
|
if (verbose) { out.print("Matching bodypart (%s) found.\n", targetBodyPart->token.c_str()); }
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Not a match; nullify the variable (it will get re-populated on the next pass through the loop)
|
||||||
|
if (verbose) { out.printerr("Bodypart \"%s\" does not match \"%s\".\n", targetBodyPart->token.c_str(), targetBodyPartCode.c_str()); }
|
||||||
|
targetBodyPart = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!targetBodyPart)
|
||||||
|
{
|
||||||
|
// Loop iteration is complete but no match was found.
|
||||||
|
out.printerr("The unit does not possess a bodypart of type \"%s\". Please check the spelling or choose a different unit.\n", targetBodyPartCode.c_str());
|
||||||
|
return CR_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for item(s)
|
||||||
|
MapCache mc;
|
||||||
|
|
||||||
|
// iterate over all items, process those where pos == pos_cursor
|
||||||
|
int itemsEquipped = 0;
|
||||||
|
int itemsFound = 0;
|
||||||
|
int numItems = world->items.all.size(); // Normally, we iterate through EVERY ITEM in the world. This is expensive, but currently necessary.
|
||||||
|
if (selected) { numItems = 1; } // If the user wants to process only the selected item, then the loop is trivialized (only one pass is needed).
|
||||||
|
for(int i=0; i< numItems; i++)
|
||||||
|
{
|
||||||
|
df::item * currentItem;
|
||||||
|
|
||||||
|
// Search behaviour depends on whether the operation is driven by cursor location or UI selection
|
||||||
|
if (selected)
|
||||||
|
{
|
||||||
|
// The "search" is trivial - the selection must always cover either one or zero items
|
||||||
|
currentItem = Gui::getSelectedItem(out);
|
||||||
|
if (!currentItem) { return CR_FAILURE; }
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Lookup the current item in the world-space
|
||||||
|
currentItem = world->items.all[i];
|
||||||
|
// Test the item's position
|
||||||
|
DFCoord pos_item(currentItem->pos.x, currentItem->pos.y, currentItem->pos.z);
|
||||||
|
if (pos_item != pos_cursor)
|
||||||
|
{
|
||||||
|
// The item is in the wrong place; skip it
|
||||||
|
// Note: we do not emit any notification, even with the "verbose" switch, because the search space is enormous and we'd invariably flood the UI with useless text
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Bypass any forbidden items
|
||||||
|
else if (currentItem->flags.bits.forbid == 1)
|
||||||
|
{
|
||||||
|
// The item is forbidden; skip it
|
||||||
|
if (verbose) { out.printerr("Forbidden item encountered; skipping to next item.\n"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test the item; check whether we have any grounds to disqualify/reject it
|
||||||
|
if (currentItem->flags.bits.in_inventory == 1)
|
||||||
|
{
|
||||||
|
// The item is in a unit's inventory; skip it
|
||||||
|
if (verbose) { out.printerr("Inventory item encountered; skipping to next item.\n"); }
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
itemsFound ++; // Track the number of items found under the cursor (for feedback purposes)
|
||||||
|
if (moveToInventory(mc, currentItem, targetUnit, targetBodyPart, ignore, multiEquipLimit, verbose))
|
||||||
|
{
|
||||||
|
// // TODO TEMP EXPERIMENTAL - try to alter the item size in order to conform to its wearer
|
||||||
|
// currentItem->getRace();
|
||||||
|
// out.print("Critter size: %d| %d | Armor size: %d", world->raws.creatures.all[targetUnit->race]->caste[targetUnit->caste]->body_size_1, world->raws.creatures.all[targetUnit->race]->caste[targetUnit->caste]->body_size_2, currentItem->getTotalDimension());
|
||||||
|
|
||||||
|
itemsEquipped++; // Track the number of items successfully processed (for feedback purposes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemsFound == 0) {
|
||||||
|
out.printerr("No usable items found at the cursor position. Please choose a different location and try again.\n");
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (itemsEquipped == 0 && !verbose) { out.printerr("Some items were found but no equipment changes could be made. Use the /verbose switch to display the reasons for failure.\n"); }
|
||||||
|
if (itemsEquipped > 0) { out.print("%d items equipped.\n", itemsEquipped); }
|
||||||
|
// At this point, some changes may have been made (such as detaching items from their original position), regardless of whether any equipment changes succeeded.
|
||||||
|
// Therefore, we must update the map.
|
||||||
|
mc.WriteAll();
|
||||||
|
|
||||||
|
// Note: we might expect to recalculate the unit's weight at this point, in order to account for the
|
||||||
|
// added items. In fact, this recalculation occurs automatically during each dwarf's "turn".
|
||||||
|
// The slight delay in recalculation is probably not worth worrying about.
|
||||||
|
|
||||||
|
// Work complete; report success
|
||||||
|
return CR_OK;
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
local _ENV = mkmodule('plugins.sort.items')
|
||||||
|
|
||||||
|
local utils = require('utils')
|
||||||
|
|
||||||
|
orders = orders or {}
|
||||||
|
|
||||||
|
-- Relies on NULL being auto-translated to NULL, and then sorted
|
||||||
|
orders.exists = {
|
||||||
|
key = function(item)
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
orders.type = {
|
||||||
|
key = function(item)
|
||||||
|
return item:getType()
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
orders.description = {
|
||||||
|
key = function(item)
|
||||||
|
return dfhack.items.getDescription(item,0)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
orders.quality = {
|
||||||
|
key = function(item)
|
||||||
|
return item:getQuality()
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
orders.improvement = {
|
||||||
|
key = function(item)
|
||||||
|
return item:getImprovementQuality()
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
orders.wear = {
|
||||||
|
key = function(item)
|
||||||
|
return item:getWear()
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
orders.material = {
|
||||||
|
key = function(item)
|
||||||
|
local mattype = item:getActualMaterial()
|
||||||
|
local matindex = item:getActualMaterialIndex()
|
||||||
|
local info = dfhack.matinfo.decode(mattype, matindex)
|
||||||
|
if info then
|
||||||
|
return info:toString()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
return _ENV
|
@ -0,0 +1,33 @@
|
|||||||
|
OPTION(DL_RUBY "download libruby from the internet" ON)
|
||||||
|
IF (DL_RUBY)
|
||||||
|
IF (UNIX)
|
||||||
|
FILE(DOWNLOAD http://cloud.github.com/downloads/jjyg/dfhack/libruby187.tar.gz ${CMAKE_CURRENT_SOURCE_DIR}/libruby187.tar.gz
|
||||||
|
EXPECTED_MD5 eb2adea59911f68e6066966c1352f291)
|
||||||
|
EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E tar xzf libruby187.tar.gz
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
FILE(RENAME libruby1.8.so.1.8.7 libruby.so)
|
||||||
|
SET(RUBYLIB libruby.so)
|
||||||
|
ELSE (UNIX)
|
||||||
|
FILE(DOWNLOAD http://cloud.github.com/downloads/jjyg/dfhack/msvcrtruby187.tar.gz ${CMAKE_CURRENT_SOURCE_DIR}/msvcrtruby187.tar.gz
|
||||||
|
EXPECTED_MD5 9f4a1659ac3a5308f32d3a1937bbeeae)
|
||||||
|
EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E tar xzf msvcrtruby187.tar.gz
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
FILE(RENAME msvcrt-ruby18.dll libruby.dll)
|
||||||
|
SET(RUBYLIB libruby.dll)
|
||||||
|
ENDIF(UNIX)
|
||||||
|
ENDIF(DL_RUBY)
|
||||||
|
|
||||||
|
ADD_CUSTOM_COMMAND(
|
||||||
|
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/ruby-autogen.rb
|
||||||
|
COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_SOURCE_DIR}/ruby-autogen.rb
|
||||||
|
DEPENDS ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl
|
||||||
|
COMMENT ruby-autogen.rb
|
||||||
|
)
|
||||||
|
ADD_CUSTOM_TARGET(ruby-autogen-rb DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/ruby-autogen.rb)
|
||||||
|
|
||||||
|
INCLUDE_DIRECTORIES("${dfhack_SOURCE_DIR}/depends/tthread")
|
||||||
|
|
||||||
|
DFHACK_PLUGIN(ruby ruby.cpp LINK_LIBRARIES dfhack-tinythread)
|
||||||
|
ADD_DEPENDENCIES(ruby ruby-autogen-rb)
|
||||||
|
|
||||||
|
INSTALL(FILES ruby.rb ruby-autogen.rb ${RUBYLIB} DESTINATION ${DFHACK_LIBRARY_DESTINATION})
|
@ -0,0 +1,105 @@
|
|||||||
|
This plugins embeds a ruby interpreter inside DFHack (ie inside Dwarf Fortress).
|
||||||
|
|
||||||
|
The plugin maps all the structures available in library/xml/ to ruby objects.
|
||||||
|
|
||||||
|
These objects are described in ruby-autogen.rb, they are all in the DFHack::
|
||||||
|
module. The toplevel 'df' method is a shortcut to the DFHack module.
|
||||||
|
|
||||||
|
The plugin does *not* map most of dfhack methods (MapCache, ...) ; only direct
|
||||||
|
access to the raw DF data structures in memory is provided.
|
||||||
|
|
||||||
|
Some library methods are stored in the ruby.rb file, with shortcuts to read a
|
||||||
|
map block, find an unit or an item, etc.
|
||||||
|
|
||||||
|
Global objects are accessible through the 'df' accessor (eg df.world).
|
||||||
|
|
||||||
|
The ruby plugin defines 2 dfhack console commands:
|
||||||
|
rb_load <filename> ; load a ruby script. Ex: rb_load hack/plants.rb (no quotes)
|
||||||
|
rb_eval <ruby expression> ; evaluate a ruby expression, show the result in the
|
||||||
|
console. Ex: rb_eval df.find_unit.name.first_name
|
||||||
|
You can use single-quotes for strings ; avoid double-quotes that are parsed
|
||||||
|
and removed by the dfhack console.
|
||||||
|
|
||||||
|
If dfhack reports 'rb_eval is not a recognized command', check stderr.log. You
|
||||||
|
need a valid 32-bit ruby library to work, and ruby1.8 is prefered (ruby1.9 may
|
||||||
|
crash DF on startup for now). Install the library in the df root folder (or
|
||||||
|
hack/ on linux), the library should be named 'libruby.dll' (.so on linux).
|
||||||
|
You can download a tested version at http://github.com/jjyg/dfhack/downloads/
|
||||||
|
|
||||||
|
The plugin also interfaces with dfhack 'onupdate' hook.
|
||||||
|
To register ruby code to be run every graphic frame, use:
|
||||||
|
handle = df.onupdate_register { puts 'i love flooding the console' }
|
||||||
|
To stop being called, use:
|
||||||
|
df.onupdate_unregister handle
|
||||||
|
|
||||||
|
The same mechanism is available for onstatechange.
|
||||||
|
|
||||||
|
|
||||||
|
Exemples
|
||||||
|
--------
|
||||||
|
|
||||||
|
For more complex exemples, check the ruby/plugins/ source folder.
|
||||||
|
|
||||||
|
Show info on the currently selected unit ('v' or 'k' DF menu)
|
||||||
|
p df.find_unit.flags1
|
||||||
|
|
||||||
|
Set a custom nickname to unit with id '123'
|
||||||
|
df.find_unit(123).name.nickname = 'moo'
|
||||||
|
|
||||||
|
Show current unit profession
|
||||||
|
p df.find_unit.profession
|
||||||
|
|
||||||
|
Change current unit profession
|
||||||
|
df.find_unit.profession = :MASON
|
||||||
|
|
||||||
|
Center the screen on unit '123'
|
||||||
|
df.center_viewscreen(df.find_unit(123))
|
||||||
|
|
||||||
|
Find an item at a given position, show its C++ classname
|
||||||
|
df.find_item(df.cursor)._rtti_classname
|
||||||
|
|
||||||
|
Find the raws name of the plant under cursor
|
||||||
|
plant = df.world.plants.all.find { |plt| df.at_cursor?(plt) }
|
||||||
|
p df.world.raws.plants.all[plant.mat_index].id
|
||||||
|
|
||||||
|
Dig a channel under the cursor
|
||||||
|
df.map_designation_at(df.cursor).dig = :Channel
|
||||||
|
df.map_block_at(df.cursor).flags.designated = true
|
||||||
|
|
||||||
|
|
||||||
|
Compilation
|
||||||
|
-----------
|
||||||
|
|
||||||
|
The plugin consists of the ruby.rb file including user comfort functions and
|
||||||
|
describing basic classes used by the autogenerated code, and ruby-autogen.rb,
|
||||||
|
the auto-generated code.
|
||||||
|
|
||||||
|
The generated code is generated by codegen.pl, which takes the codegen.out.xml
|
||||||
|
file as input.
|
||||||
|
|
||||||
|
For exemple,
|
||||||
|
<ld:global-type ld:meta="struct-type" type-name="unit">
|
||||||
|
<ld:field type-name="language_name" name="name" ld:meta="global"/>
|
||||||
|
<ld:field name="custom_profession" ld:meta="primitive" ld:subtype="stl-string"/>
|
||||||
|
<ld:field ld:subtype="enum" base-type="int16_t" name="profession" type-name="profession" ld:meta="global"/>
|
||||||
|
|
||||||
|
Will generate
|
||||||
|
class Unit < MemHack::Compound
|
||||||
|
field(:name, 0) { global :LanguageName }
|
||||||
|
field(:custom_profession, 60) { stl_string }
|
||||||
|
field(:profession, 64) { number 16, true }
|
||||||
|
|
||||||
|
The syntax for the 'field' method is:
|
||||||
|
1st argument = name of the method
|
||||||
|
2nd argument = offset of this field from the beginning of the struct.
|
||||||
|
|
||||||
|
The block argument describes the type of the field: uint32, ptr to global...
|
||||||
|
|
||||||
|
Primitive type access is done through native methods in ruby.cpp (vector length,
|
||||||
|
raw memory access, etc)
|
||||||
|
|
||||||
|
MemHack::Pointers are automatically dereferenced ; so a vector of pointer to
|
||||||
|
Units will yield Units directly. Null pointers yield the 'nil' value.
|
||||||
|
|
||||||
|
This allows to use code such as 'df.world.units.all[0].pos', with 'all' being
|
||||||
|
in fact a vector of *pointers* to DFHack::Unit objects.
|
@ -0,0 +1,912 @@
|
|||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use XML::LibXML;
|
||||||
|
|
||||||
|
our @lines_rb;
|
||||||
|
|
||||||
|
my $os;
|
||||||
|
if ($^O =~ /linux/i) {
|
||||||
|
$os = 'linux';
|
||||||
|
} else {
|
||||||
|
$os = 'windows';
|
||||||
|
}
|
||||||
|
|
||||||
|
sub indent_rb(&) {
|
||||||
|
my ($sub) = @_;
|
||||||
|
my @lines;
|
||||||
|
{
|
||||||
|
local @lines_rb;
|
||||||
|
$sub->();
|
||||||
|
@lines = map { " " . $_ } @lines_rb;
|
||||||
|
}
|
||||||
|
push @lines_rb, @lines
|
||||||
|
}
|
||||||
|
|
||||||
|
sub rb_ucase {
|
||||||
|
my ($name) = @_;
|
||||||
|
return $name if ($name eq uc($name));
|
||||||
|
return join("", map { ucfirst $_ } (split('_', $name)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
my %item_renderer = (
|
||||||
|
'global' => \&render_item_global,
|
||||||
|
'number' => \&render_item_number,
|
||||||
|
'container' => \&render_item_container,
|
||||||
|
'compound' => \&render_item_compound,
|
||||||
|
'pointer' => \&render_item_pointer,
|
||||||
|
'static-array' => \&render_item_staticarray,
|
||||||
|
'primitive' => \&render_item_primitive,
|
||||||
|
'bytes' => \&render_item_bytes,
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
my %global_types;
|
||||||
|
our $current_typename;
|
||||||
|
|
||||||
|
sub render_global_enum {
|
||||||
|
my ($name, $type) = @_;
|
||||||
|
|
||||||
|
my $rbname = rb_ucase($name);
|
||||||
|
push @lines_rb, "class $rbname < MemHack::Enum";
|
||||||
|
indent_rb {
|
||||||
|
render_enum_fields($type);
|
||||||
|
};
|
||||||
|
push @lines_rb, "end\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
sub render_enum_fields {
|
||||||
|
my ($type) = @_;
|
||||||
|
|
||||||
|
push @lines_rb, "ENUM = Hash.new";
|
||||||
|
push @lines_rb, "NUME = Hash.new";
|
||||||
|
|
||||||
|
my %attr_type;
|
||||||
|
my %attr_list;
|
||||||
|
render_enum_initattrs($type, \%attr_type, \%attr_list);
|
||||||
|
|
||||||
|
my $value = -1;
|
||||||
|
for my $item ($type->findnodes('child::enum-item'))
|
||||||
|
{
|
||||||
|
$value = $item->getAttribute('value') || ($value+1);
|
||||||
|
my $elemname = $item->getAttribute('name'); # || "unk_$value";
|
||||||
|
|
||||||
|
if ($elemname)
|
||||||
|
{
|
||||||
|
my $rbelemname = rb_ucase($elemname);
|
||||||
|
push @lines_rb, "ENUM[$value] = :$rbelemname ; NUME[:$rbelemname] = $value";
|
||||||
|
for my $iattr ($item->findnodes('child::item-attr'))
|
||||||
|
{
|
||||||
|
my $attr = render_enum_attr($rbelemname, $iattr, \%attr_type, \%attr_list);
|
||||||
|
$lines_rb[$#lines_rb] .= ' ; ' . $attr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub render_enum_initattrs {
|
||||||
|
my ($type, $attr_type, $attr_list) = @_;
|
||||||
|
|
||||||
|
for my $attr ($type->findnodes('child::enum-attr'))
|
||||||
|
{
|
||||||
|
my $rbattr = rb_ucase($attr->getAttribute('name'));
|
||||||
|
my $typeattr = $attr->getAttribute('type-name');
|
||||||
|
# find how we need to encode the attribute values: string, symbol (for enums), raw (number, bool)
|
||||||
|
if ($typeattr) {
|
||||||
|
if ($global_types{$typeattr}) {
|
||||||
|
$attr_type->{$rbattr} = 'symbol';
|
||||||
|
} else {
|
||||||
|
$attr_type->{$rbattr} = 'naked';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$attr_type->{$rbattr} = 'quote';
|
||||||
|
}
|
||||||
|
|
||||||
|
my $def = $attr->getAttribute('default-value');
|
||||||
|
if ($attr->getAttribute('is-list'))
|
||||||
|
{
|
||||||
|
push @lines_rb, "$rbattr = Hash.new { |h, k| h[k] = [] }";
|
||||||
|
$attr_list->{$rbattr} = 1;
|
||||||
|
}
|
||||||
|
elsif ($def)
|
||||||
|
{
|
||||||
|
$def = ":$def" if ($attr_type->{$rbattr} eq 'symbol');
|
||||||
|
$def =~ s/'/\\'/g if ($attr_type->{$rbattr} eq 'quote');
|
||||||
|
$def = "'$def'" if ($attr_type->{$rbattr} eq 'quote');
|
||||||
|
push @lines_rb, "$rbattr = Hash.new($def)";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
push @lines_rb, "$rbattr = Hash.new";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub render_enum_attr {
|
||||||
|
my ($rbelemname, $iattr, $attr_type, $attr_list) = @_;
|
||||||
|
|
||||||
|
my $ian = $iattr->getAttribute('name');
|
||||||
|
my $iav = $iattr->getAttribute('value');
|
||||||
|
my $rbattr = rb_ucase($ian);
|
||||||
|
|
||||||
|
my $op = ($attr_list->{$rbattr} ? '<<' : '=');
|
||||||
|
|
||||||
|
$iav = ":$iav" if ($attr_type->{$rbattr} eq 'symbol');
|
||||||
|
$iav =~ s/'/\\'/g if ($attr_type->{$rbattr} eq 'quote');
|
||||||
|
$iav = "'$iav'" if ($attr_type->{$rbattr} eq 'quote');
|
||||||
|
|
||||||
|
return "${rbattr}[:$rbelemname] $op $iav";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sub render_global_bitfield {
|
||||||
|
my ($name, $type) = @_;
|
||||||
|
|
||||||
|
my $rbname = rb_ucase($name);
|
||||||
|
push @lines_rb, "class $rbname < MemHack::Compound";
|
||||||
|
indent_rb {
|
||||||
|
render_bitfield_fields($type);
|
||||||
|
};
|
||||||
|
push @lines_rb, "end\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
sub render_bitfield_fields {
|
||||||
|
my ($type) = @_;
|
||||||
|
|
||||||
|
push @lines_rb, "field(:_whole, 0) {";
|
||||||
|
indent_rb {
|
||||||
|
render_item_number($type);
|
||||||
|
};
|
||||||
|
push @lines_rb, "}";
|
||||||
|
|
||||||
|
my $shift = 0;
|
||||||
|
for my $field ($type->findnodes('child::ld:field'))
|
||||||
|
{
|
||||||
|
my $count = $field->getAttribute('count') || 1;
|
||||||
|
my $name = $field->getAttribute('name');
|
||||||
|
my $type = $field->getAttribute('type-name');
|
||||||
|
my $enum = rb_ucase($type) if ($type and $global_types{$type});
|
||||||
|
$name = $field->getAttribute('ld:anon-name') if (!$name);
|
||||||
|
print "bitfield $name !number\n" if (!($field->getAttribute('ld:meta') eq 'number'));
|
||||||
|
|
||||||
|
if ($name)
|
||||||
|
{
|
||||||
|
if ($count == 1) {
|
||||||
|
push @lines_rb, "field(:$name, 0) { bit $shift }";
|
||||||
|
} elsif ($enum) {
|
||||||
|
push @lines_rb, "field(:$name, 0) { bits $shift, $count, $enum }";
|
||||||
|
} else {
|
||||||
|
push @lines_rb, "field(:$name, 0) { bits $shift, $count }";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$shift += $count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
my %seen_class;
|
||||||
|
our $compound_off;
|
||||||
|
our $compound_pointer;
|
||||||
|
|
||||||
|
sub render_global_class {
|
||||||
|
my ($name, $type) = @_;
|
||||||
|
|
||||||
|
my $meta = $type->getAttribute('ld:meta');
|
||||||
|
my $rbname = rb_ucase($name);
|
||||||
|
|
||||||
|
# ensure pre-definition of ancestors
|
||||||
|
my $parent = $type->getAttribute('inherits-from');
|
||||||
|
render_global_class($parent, $global_types{$parent}) if ($parent and !$seen_class{$parent});
|
||||||
|
|
||||||
|
return if $seen_class{$name};
|
||||||
|
$seen_class{$name}++;
|
||||||
|
|
||||||
|
local $compound_off = 0;
|
||||||
|
$compound_off = 4 if ($meta eq 'class-type');
|
||||||
|
$compound_off = sizeof($global_types{$parent}) if $parent;
|
||||||
|
local $current_typename = $rbname;
|
||||||
|
|
||||||
|
my $rtti_name;
|
||||||
|
if ($meta eq 'class-type')
|
||||||
|
{
|
||||||
|
$rtti_name = $type->getAttribute('original-name') ||
|
||||||
|
$type->getAttribute('type-name') ||
|
||||||
|
$name;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $rbparent = ($parent ? rb_ucase($parent) : 'MemHack::Compound');
|
||||||
|
push @lines_rb, "class $rbname < $rbparent";
|
||||||
|
indent_rb {
|
||||||
|
my $sz = sizeof($type);
|
||||||
|
# see comment is sub sizeof ; but gcc has sizeof(cls) aligned
|
||||||
|
$sz = align_field($sz, 4) if $os eq 'linux' and $meta eq 'class-type';
|
||||||
|
push @lines_rb, "sizeof $sz\n";
|
||||||
|
|
||||||
|
push @lines_rb, "rtti_classname :$rtti_name\n" if $rtti_name;
|
||||||
|
|
||||||
|
render_struct_fields($type);
|
||||||
|
|
||||||
|
my $vms = $type->findnodes('child::virtual-methods')->[0];
|
||||||
|
render_class_vmethods($vms) if $vms;
|
||||||
|
};
|
||||||
|
push @lines_rb, "end\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
sub render_struct_fields {
|
||||||
|
my ($type) = @_;
|
||||||
|
|
||||||
|
my $isunion = $type->getAttribute('is-union');
|
||||||
|
|
||||||
|
for my $field ($type->findnodes('child::ld:field'))
|
||||||
|
{
|
||||||
|
my $name = $field->getAttribute('name');
|
||||||
|
$name = $field->getAttribute('ld:anon-name') if (!$name);
|
||||||
|
|
||||||
|
if (!$name and $field->getAttribute('ld:anon-compound'))
|
||||||
|
{
|
||||||
|
render_struct_fields($field);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$compound_off = align_field($compound_off, get_field_align($field));
|
||||||
|
if ($name)
|
||||||
|
{
|
||||||
|
push @lines_rb, "field(:$name, $compound_off) {";
|
||||||
|
indent_rb {
|
||||||
|
render_item($field);
|
||||||
|
};
|
||||||
|
push @lines_rb, "}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$compound_off += sizeof($field) if (!$isunion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub render_class_vmethods {
|
||||||
|
my ($vms) = @_;
|
||||||
|
my $voff = 0;
|
||||||
|
|
||||||
|
for my $meth ($vms->findnodes('child::vmethod'))
|
||||||
|
{
|
||||||
|
my $name = $meth->getAttribute('name');
|
||||||
|
|
||||||
|
if ($name)
|
||||||
|
{
|
||||||
|
my @argnames;
|
||||||
|
my @argargs;
|
||||||
|
|
||||||
|
# check if arguments need special treatment (eg auto-convert from symbol to enum value)
|
||||||
|
for my $arg ($meth->findnodes('child::ld:field'))
|
||||||
|
{
|
||||||
|
my $nr = $#argnames + 1;
|
||||||
|
my $argname = lcfirst($arg->getAttribute('name') || "arg$nr");
|
||||||
|
push @argnames, $argname;
|
||||||
|
|
||||||
|
if ($arg->getAttribute('ld:meta') eq 'global' and $arg->getAttribute('ld:subtype') eq 'enum') {
|
||||||
|
push @argargs, rb_ucase($arg->getAttribute('type-name')) . ".int($argname)";
|
||||||
|
} else {
|
||||||
|
push @argargs, $argname;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# write vmethod ruby wrapper
|
||||||
|
push @lines_rb, "def $name(" . join(', ', @argnames) . ')';
|
||||||
|
indent_rb {
|
||||||
|
my $args = join('', map { ", $_" } @argargs);
|
||||||
|
my $call = "DFHack.vmethod_call(self, $voff$args)";
|
||||||
|
my $ret = $meth->findnodes('child::ret-type')->[0];
|
||||||
|
render_class_vmethod_ret($call, $ret);
|
||||||
|
};
|
||||||
|
push @lines_rb, 'end';
|
||||||
|
}
|
||||||
|
|
||||||
|
# on linux, the destructor uses 2 entries
|
||||||
|
$voff += 4 if $meth->getAttribute('is-destructor') and $os eq 'linux';
|
||||||
|
$voff += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub render_class_vmethod_ret {
|
||||||
|
my ($call, $ret) = @_;
|
||||||
|
|
||||||
|
if (!$ret)
|
||||||
|
{
|
||||||
|
# method returns void, hide return value
|
||||||
|
push @lines_rb, "$call ; nil";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $retmeta = $ret->getAttribute('ld:meta') || '';
|
||||||
|
if ($retmeta eq 'global')
|
||||||
|
{
|
||||||
|
# method returns an enum value: auto-convert to symbol
|
||||||
|
my $retname = $ret->getAttribute('type-name');
|
||||||
|
if ($retname and $global_types{$retname} and
|
||||||
|
$global_types{$retname}->getAttribute('ld:meta') eq 'enum-type')
|
||||||
|
{
|
||||||
|
push @lines_rb, rb_ucase($retname) . ".sym($call)";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
print "vmethod global nonenum $call\n";
|
||||||
|
push @lines_rb, $call;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
elsif ($retmeta eq 'number')
|
||||||
|
{
|
||||||
|
# raw method call returns an int32, mask according to actual return type
|
||||||
|
my $retsubtype = $ret->getAttribute('ld:subtype');
|
||||||
|
my $retbits = $ret->getAttribute('ld:bits');
|
||||||
|
push @lines_rb, "val = $call";
|
||||||
|
if ($retsubtype eq 'bool')
|
||||||
|
{
|
||||||
|
push @lines_rb, "(val & 1) != 0";
|
||||||
|
}
|
||||||
|
elsif ($ret->getAttribute('ld:unsigned'))
|
||||||
|
{
|
||||||
|
push @lines_rb, "val & ((1 << $retbits) - 1)";
|
||||||
|
}
|
||||||
|
elsif ($retbits != 32)
|
||||||
|
{
|
||||||
|
# manual sign extension
|
||||||
|
push @lines_rb, "val &= ((1 << $retbits) - 1)";
|
||||||
|
push @lines_rb, "((val >> ($retbits-1)) & 1) == 0 ? val : val - (1 << $retbits)";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
elsif ($retmeta eq 'pointer')
|
||||||
|
{
|
||||||
|
# method returns a pointer to some struct, create the correct ruby wrapper
|
||||||
|
push @lines_rb, "ptr = $call";
|
||||||
|
push @lines_rb, "class << self";
|
||||||
|
indent_rb {
|
||||||
|
render_item($ret->findnodes('child::ld:item')->[0]);
|
||||||
|
};
|
||||||
|
push @lines_rb, "end._at(ptr) if ptr != 0";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
print "vmethod unkret $call\n";
|
||||||
|
push @lines_rb, $call;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub render_global_objects {
|
||||||
|
my (@objects) = @_;
|
||||||
|
my @global_objects;
|
||||||
|
|
||||||
|
local $compound_off = 0;
|
||||||
|
local $current_typename = 'Global';
|
||||||
|
|
||||||
|
# define all globals as 'fields' of a virtual globalobject wrapping the whole address space
|
||||||
|
push @lines_rb, 'class GlobalObjects < MemHack::Compound';
|
||||||
|
indent_rb {
|
||||||
|
for my $obj (@objects)
|
||||||
|
{
|
||||||
|
my $oname = $obj->getAttribute('name');
|
||||||
|
|
||||||
|
# check if the symbol is defined in xml to avoid NULL deref
|
||||||
|
my $addr = "DFHack.get_global_address('$oname')";
|
||||||
|
push @lines_rb, "addr = $addr";
|
||||||
|
push @lines_rb, "if addr != 0";
|
||||||
|
indent_rb {
|
||||||
|
push @lines_rb, "field(:$oname, addr) {";
|
||||||
|
my $item = $obj->findnodes('child::ld:item')->[0];
|
||||||
|
indent_rb {
|
||||||
|
render_item($item);
|
||||||
|
};
|
||||||
|
push @lines_rb, "}";
|
||||||
|
};
|
||||||
|
push @lines_rb, "end";
|
||||||
|
|
||||||
|
push @global_objects, $oname;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
push @lines_rb, "end";
|
||||||
|
|
||||||
|
# define friendlier accessors, eg df.world -> DFHack::GlobalObjects.new._at(0).world
|
||||||
|
indent_rb {
|
||||||
|
push @lines_rb, "Global = GlobalObjects.new._at(0)";
|
||||||
|
for my $obj (@global_objects)
|
||||||
|
{
|
||||||
|
push @lines_rb, "def self.$obj ; Global.$obj ; end";
|
||||||
|
push @lines_rb, "def self.$obj=(v) ; Global.$obj = v ; end";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
my %align_cache;
|
||||||
|
my %sizeof_cache;
|
||||||
|
|
||||||
|
sub align_field {
|
||||||
|
my ($off, $fldalign) = @_;
|
||||||
|
my $dt = $off % $fldalign;
|
||||||
|
$off += $fldalign - $dt if $dt > 0;
|
||||||
|
return $off;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_field_align {
|
||||||
|
my ($field) = @_;
|
||||||
|
my $al = 4;
|
||||||
|
my $meta = $field->getAttribute('ld:meta');
|
||||||
|
|
||||||
|
if ($meta eq 'number') {
|
||||||
|
$al = $field->getAttribute('ld:bits')/8;
|
||||||
|
$al = 4 if $al > 4;
|
||||||
|
} elsif ($meta eq 'global') {
|
||||||
|
$al = get_global_align($field);
|
||||||
|
} elsif ($meta eq 'compound') {
|
||||||
|
$al = get_compound_align($field);
|
||||||
|
} elsif ($meta eq 'static-array') {
|
||||||
|
my $tg = $field->findnodes('child::ld:item')->[0];
|
||||||
|
$al = get_field_align($tg);
|
||||||
|
} elsif ($meta eq 'bytes') {
|
||||||
|
$al = $field->getAttribute('alignment') || 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $al;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_global_align {
|
||||||
|
my ($field) = @_;
|
||||||
|
|
||||||
|
my $typename = $field->getAttribute('type-name');
|
||||||
|
return $align_cache{$typename} if $align_cache{$typename};
|
||||||
|
|
||||||
|
my $g = $global_types{$typename};
|
||||||
|
|
||||||
|
my $st = $field->getAttribute('ld:subtype') || '';
|
||||||
|
if ($st eq 'bitfield' or $st eq 'enum' or $g->getAttribute('ld:meta') eq 'bitfield-type')
|
||||||
|
{
|
||||||
|
my $base = $field->getAttribute('base-type') || $g->getAttribute('base-type') || 'uint32_t';
|
||||||
|
print "$st type $base\n" if $base !~ /int(\d+)_t/;
|
||||||
|
# dont cache, field->base-type may differ
|
||||||
|
return $1/8;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $al = 1;
|
||||||
|
for my $gf ($g->findnodes('child::ld:field')) {
|
||||||
|
my $fld_al = get_field_align($gf);
|
||||||
|
$al = $fld_al if $fld_al > $al;
|
||||||
|
}
|
||||||
|
$align_cache{$typename} = $al;
|
||||||
|
|
||||||
|
return $al;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_compound_align {
|
||||||
|
my ($field) = @_;
|
||||||
|
|
||||||
|
my $st = $field->getAttribute('ld:subtype') || '';
|
||||||
|
if ($st eq 'bitfield' or $st eq 'enum')
|
||||||
|
{
|
||||||
|
my $base = $field->getAttribute('base-type') || 'uint32_t';
|
||||||
|
print "$st type $base\n" if $base !~ /int(\d+)_t/;
|
||||||
|
return $1/8;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $al = 1;
|
||||||
|
for my $f ($field->findnodes('child::ld:field')) {
|
||||||
|
my $fal = get_field_align($f);
|
||||||
|
$al = $fal if $fal > $al;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $al;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub sizeof {
|
||||||
|
my ($field) = @_;
|
||||||
|
my $meta = $field->getAttribute('ld:meta');
|
||||||
|
|
||||||
|
if ($meta eq 'number') {
|
||||||
|
return $field->getAttribute('ld:bits')/8;
|
||||||
|
|
||||||
|
} elsif ($meta eq 'pointer') {
|
||||||
|
return 4;
|
||||||
|
|
||||||
|
} elsif ($meta eq 'static-array') {
|
||||||
|
my $count = $field->getAttribute('count');
|
||||||
|
my $tg = $field->findnodes('child::ld:item')->[0];
|
||||||
|
return $count * sizeof($tg);
|
||||||
|
|
||||||
|
} elsif ($meta eq 'bitfield-type' or $meta eq 'enum-type') {
|
||||||
|
my $base = $field->getAttribute('base-type') || 'uint32_t';
|
||||||
|
print "$meta type $base\n" if $base !~ /int(\d+)_t/;
|
||||||
|
return $1/8;
|
||||||
|
|
||||||
|
} elsif ($meta eq 'global') {
|
||||||
|
my $typename = $field->getAttribute('type-name');
|
||||||
|
return $sizeof_cache{$typename} if $sizeof_cache{$typename};
|
||||||
|
|
||||||
|
my $g = $global_types{$typename};
|
||||||
|
my $st = $field->getAttribute('ld:subtype') || '';
|
||||||
|
if ($st eq 'bitfield' or $st eq 'enum' or $g->getAttribute('ld:meta') eq 'bitfield-type')
|
||||||
|
{
|
||||||
|
my $base = $field->getAttribute('base-type') || $g->getAttribute('base-type') || 'uint32_t';
|
||||||
|
print "$st type $base\n" if $base !~ /int(\d+)_t/;
|
||||||
|
return $1/8;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sizeof($g);
|
||||||
|
|
||||||
|
} elsif ($meta eq 'class-type' or $meta eq 'struct-type' or $meta eq 'compound') {
|
||||||
|
return sizeof_compound($field);
|
||||||
|
|
||||||
|
} elsif ($meta eq 'container') {
|
||||||
|
my $subtype = $field->getAttribute('ld:subtype');
|
||||||
|
|
||||||
|
if ($subtype eq 'stl-vector') {
|
||||||
|
if ($os eq 'linux') {
|
||||||
|
return 12;
|
||||||
|
} elsif ($os eq 'windows') {
|
||||||
|
return 16;
|
||||||
|
} else {
|
||||||
|
print "sizeof stl-vector on $os\n";
|
||||||
|
}
|
||||||
|
} elsif ($subtype eq 'stl-bit-vector') {
|
||||||
|
if ($os eq 'linux') {
|
||||||
|
return 20;
|
||||||
|
} elsif ($os eq 'windows') {
|
||||||
|
return 20;
|
||||||
|
} else {
|
||||||
|
print "sizeof stl-bit-vector on $os\n";
|
||||||
|
}
|
||||||
|
} elsif ($subtype eq 'stl-deque') {
|
||||||
|
if ($os eq 'linux') {
|
||||||
|
return 40;
|
||||||
|
} elsif ($os eq 'windows') {
|
||||||
|
return 24;
|
||||||
|
} else {
|
||||||
|
print "sizeof stl-deque on $os\n";
|
||||||
|
}
|
||||||
|
} elsif ($subtype eq 'df-linked-list') {
|
||||||
|
return 12;
|
||||||
|
} elsif ($subtype eq 'df-flagarray') {
|
||||||
|
return 8;
|
||||||
|
} elsif ($subtype eq 'df-array') {
|
||||||
|
return 8; # XXX 6 ?
|
||||||
|
} else {
|
||||||
|
print "sizeof container $subtype\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
} elsif ($meta eq 'primitive') {
|
||||||
|
my $subtype = $field->getAttribute('ld:subtype');
|
||||||
|
|
||||||
|
if ($subtype eq 'stl-string') { if ($os eq 'linux') {
|
||||||
|
return 4;
|
||||||
|
} elsif ($os eq 'windows') {
|
||||||
|
return 28;
|
||||||
|
} else {
|
||||||
|
print "sizeof stl-string on $os\n";
|
||||||
|
}
|
||||||
|
print "sizeof stl-string\n";
|
||||||
|
} else {
|
||||||
|
print "sizeof primitive $subtype\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
} elsif ($meta eq 'bytes') {
|
||||||
|
return $field->getAttribute('size');
|
||||||
|
} else {
|
||||||
|
print "sizeof $meta\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub sizeof_compound {
|
||||||
|
my ($field) = @_;
|
||||||
|
|
||||||
|
my $typename = $field->getAttribute('type-name');
|
||||||
|
return $sizeof_cache{$typename} if $typename and $sizeof_cache{$typename};
|
||||||
|
|
||||||
|
my $meta = $field->getAttribute('ld:meta');
|
||||||
|
|
||||||
|
my $st = $field->getAttribute('ld:subtype') || '';
|
||||||
|
if ($st eq 'bitfield' or $st eq 'enum')
|
||||||
|
{
|
||||||
|
my $base = $field->getAttribute('base-type') || 'uint32_t';
|
||||||
|
print "$st type $base\n" if $base !~ /int(\d+)_t/;
|
||||||
|
$sizeof_cache{$typename} = $1/8 if $typename;
|
||||||
|
return $1/8;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($field->getAttribute('is-union'))
|
||||||
|
{
|
||||||
|
my $sz = 0;
|
||||||
|
for my $f ($field->findnodes('child::ld:field'))
|
||||||
|
{
|
||||||
|
my $fsz = sizeof($f);
|
||||||
|
$sz = $fsz if $fsz > $sz;
|
||||||
|
}
|
||||||
|
return $sz;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $parent = $field->getAttribute('inherits-from');
|
||||||
|
my $off = 0;
|
||||||
|
$off = 4 if ($meta eq 'class-type');
|
||||||
|
$off = sizeof($global_types{$parent}) if ($parent);
|
||||||
|
|
||||||
|
my $al = 1;
|
||||||
|
$al = 4 if ($meta eq 'class-type');
|
||||||
|
|
||||||
|
for my $f ($field->findnodes('child::ld:field'))
|
||||||
|
{
|
||||||
|
my $fa = get_field_align($f);
|
||||||
|
$al = $fa if $fa > $al;
|
||||||
|
$off = align_field($off, $fa);
|
||||||
|
$off += sizeof($f);
|
||||||
|
}
|
||||||
|
|
||||||
|
# GCC: class a { vtable; char; } ; class b:a { char c2; } -> c2 has offset 5 (Windows MSVC: offset 8)
|
||||||
|
$al = 1 if ($meta eq 'class-type' and $os eq 'linux');
|
||||||
|
$off = align_field($off, $al);
|
||||||
|
$sizeof_cache{$typename} = $off if $typename;
|
||||||
|
|
||||||
|
return $off;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sub render_item {
|
||||||
|
my ($item) = @_;
|
||||||
|
return if (!$item);
|
||||||
|
|
||||||
|
my $meta = $item->getAttribute('ld:meta');
|
||||||
|
|
||||||
|
my $renderer = $item_renderer{$meta};
|
||||||
|
if ($renderer) {
|
||||||
|
$renderer->($item);
|
||||||
|
} else {
|
||||||
|
print "no render item $meta\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub render_item_global {
|
||||||
|
my ($item) = @_;
|
||||||
|
|
||||||
|
my $typename = $item->getAttribute('type-name');
|
||||||
|
my $subtype = $item->getAttribute('ld:subtype');
|
||||||
|
|
||||||
|
if ($subtype and $subtype eq 'enum') {
|
||||||
|
render_item_number($item);
|
||||||
|
} else {
|
||||||
|
my $rbname = rb_ucase($typename);
|
||||||
|
push @lines_rb, "global :$rbname";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub render_item_number {
|
||||||
|
my ($item, $classname) = @_;
|
||||||
|
|
||||||
|
my $subtype = $item->getAttribute('ld:subtype');
|
||||||
|
my $meta = $item->getAttribute('ld:meta');
|
||||||
|
my $initvalue = $item->getAttribute('init-value');
|
||||||
|
my $typename = $item->getAttribute('type-name');
|
||||||
|
undef $typename if ($meta and $meta eq 'bitfield-type');
|
||||||
|
$typename = rb_ucase($typename) if $typename;
|
||||||
|
$typename = $classname if (!$typename and $subtype and $subtype eq 'enum'); # compound enum
|
||||||
|
|
||||||
|
$initvalue = 1 if ($initvalue and $initvalue eq 'true');
|
||||||
|
$initvalue = ":$initvalue" if ($initvalue and $typename and $initvalue =~ /[a-zA-Z]/);
|
||||||
|
$initvalue ||= 'nil' if $typename;
|
||||||
|
|
||||||
|
$subtype = $item->getAttribute('base-type') if (!$subtype or $subtype eq 'bitfield' or $subtype eq 'enum');
|
||||||
|
$subtype = 'int32_t' if (!$subtype);
|
||||||
|
|
||||||
|
if ($subtype eq 'int64_t') {
|
||||||
|
push @lines_rb, 'number 64, true';
|
||||||
|
} elsif ($subtype eq 'uint32_t') {
|
||||||
|
push @lines_rb, 'number 32, false';
|
||||||
|
} elsif ($subtype eq 'int32_t') {
|
||||||
|
push @lines_rb, 'number 32, true';
|
||||||
|
} elsif ($subtype eq 'uint16_t') {
|
||||||
|
push @lines_rb, 'number 16, false';
|
||||||
|
} elsif ($subtype eq 'int16_t') {
|
||||||
|
push @lines_rb, 'number 16, true';
|
||||||
|
} elsif ($subtype eq 'uint8_t') {
|
||||||
|
push @lines_rb, 'number 8, false';
|
||||||
|
} elsif ($subtype eq 'int8_t') {
|
||||||
|
push @lines_rb, 'number 8, false';
|
||||||
|
} elsif ($subtype eq 'bool') {
|
||||||
|
push @lines_rb, 'number 8, true';
|
||||||
|
$initvalue ||= 'nil';
|
||||||
|
$typename ||= 'BooleanEnum';
|
||||||
|
} elsif ($subtype eq 's-float') {
|
||||||
|
push @lines_rb, 'float';
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
print "no render number $subtype\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines_rb[$#lines_rb] .= ", $initvalue" if ($initvalue);
|
||||||
|
$lines_rb[$#lines_rb] .= ", $typename" if ($typename);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub render_item_compound {
|
||||||
|
my ($item) = @_;
|
||||||
|
|
||||||
|
my $subtype = $item->getAttribute('ld:subtype');
|
||||||
|
|
||||||
|
local $compound_off = 0;
|
||||||
|
my $classname = $current_typename . '_' . rb_ucase($item->getAttribute('ld:typedef-name'));
|
||||||
|
local $current_typename = $classname;
|
||||||
|
|
||||||
|
if (!$subtype || $subtype eq 'bitfield')
|
||||||
|
{
|
||||||
|
push @lines_rb, "compound(:$classname) {";
|
||||||
|
indent_rb {
|
||||||
|
# declare sizeof() only for useful compound, eg the one behind pointers
|
||||||
|
# that the user may want to allocate
|
||||||
|
my $sz = sizeof($item);
|
||||||
|
push @lines_rb, "sizeof $sz\n" if $compound_pointer;
|
||||||
|
|
||||||
|
if (!$subtype) {
|
||||||
|
local $compound_pointer = 0;
|
||||||
|
render_struct_fields($item);
|
||||||
|
} else {
|
||||||
|
render_bitfield_fields($item);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
push @lines_rb, "}"
|
||||||
|
}
|
||||||
|
elsif ($subtype eq 'enum')
|
||||||
|
{
|
||||||
|
push @lines_rb, "class ::DFHack::$classname < MemHack::Enum";
|
||||||
|
indent_rb {
|
||||||
|
# declare constants
|
||||||
|
render_enum_fields($item);
|
||||||
|
};
|
||||||
|
push @lines_rb, "end\n";
|
||||||
|
|
||||||
|
# actual field
|
||||||
|
render_item_number($item, $classname);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
print "no render compound $subtype\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub render_item_container {
|
||||||
|
my ($item) = @_;
|
||||||
|
|
||||||
|
my $subtype = $item->getAttribute('ld:subtype');
|
||||||
|
my $rbmethod = join('_', split('-', $subtype));
|
||||||
|
my $tg = $item->findnodes('child::ld:item')->[0];
|
||||||
|
my $indexenum = $item->getAttribute('index-enum');
|
||||||
|
if ($tg)
|
||||||
|
{
|
||||||
|
if ($rbmethod eq 'df_linked_list') {
|
||||||
|
push @lines_rb, "$rbmethod {";
|
||||||
|
} else {
|
||||||
|
my $tglen = sizeof($tg) if $tg;
|
||||||
|
push @lines_rb, "$rbmethod($tglen) {";
|
||||||
|
}
|
||||||
|
indent_rb {
|
||||||
|
render_item($tg);
|
||||||
|
};
|
||||||
|
push @lines_rb, "}";
|
||||||
|
}
|
||||||
|
elsif ($indexenum)
|
||||||
|
{
|
||||||
|
$indexenum = rb_ucase($indexenum);
|
||||||
|
push @lines_rb, "$rbmethod($indexenum)";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
push @lines_rb, "$rbmethod";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub render_item_pointer {
|
||||||
|
my ($item) = @_;
|
||||||
|
|
||||||
|
my $tg = $item->findnodes('child::ld:item')->[0];
|
||||||
|
my $ary = $item->getAttribute('is-array') || '';
|
||||||
|
|
||||||
|
if ($ary eq 'true') {
|
||||||
|
my $tglen = sizeof($tg) if $tg;
|
||||||
|
push @lines_rb, "pointer_ary($tglen) {";
|
||||||
|
} else {
|
||||||
|
push @lines_rb, "pointer {";
|
||||||
|
}
|
||||||
|
indent_rb {
|
||||||
|
local $compound_pointer = 1;
|
||||||
|
render_item($tg);
|
||||||
|
};
|
||||||
|
push @lines_rb, "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
sub render_item_staticarray {
|
||||||
|
my ($item) = @_;
|
||||||
|
|
||||||
|
my $count = $item->getAttribute('count');
|
||||||
|
my $tg = $item->findnodes('child::ld:item')->[0];
|
||||||
|
my $tglen = sizeof($tg) if $tg;
|
||||||
|
my $indexenum = $item->getAttribute('index-enum');
|
||||||
|
|
||||||
|
if ($indexenum) {
|
||||||
|
$indexenum = rb_ucase($indexenum);
|
||||||
|
push @lines_rb, "static_array($count, $tglen, $indexenum) {";
|
||||||
|
} else {
|
||||||
|
push @lines_rb, "static_array($count, $tglen) {";
|
||||||
|
}
|
||||||
|
indent_rb {
|
||||||
|
render_item($tg);
|
||||||
|
};
|
||||||
|
push @lines_rb, "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
sub render_item_primitive {
|
||||||
|
my ($item) = @_;
|
||||||
|
|
||||||
|
my $subtype = $item->getAttribute('ld:subtype');
|
||||||
|
if ($subtype eq 'stl-string') {
|
||||||
|
push @lines_rb, "stl_string";
|
||||||
|
} else {
|
||||||
|
print "no render primitive $subtype\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub render_item_bytes {
|
||||||
|
my ($item) = @_;
|
||||||
|
|
||||||
|
my $subtype = $item->getAttribute('ld:subtype');
|
||||||
|
if ($subtype eq 'padding') {
|
||||||
|
} elsif ($subtype eq 'static-string') {
|
||||||
|
my $size = $item->getAttribute('size');
|
||||||
|
push @lines_rb, "static_string($size)";
|
||||||
|
} else {
|
||||||
|
print "no render bytes $subtype\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
my $input = $ARGV[0] or die "need input xml";
|
||||||
|
my $output = $ARGV[1] or die "need output file";
|
||||||
|
|
||||||
|
my $doc = XML::LibXML->new()->parse_file($input);
|
||||||
|
$global_types{$_->getAttribute('type-name')} = $_ foreach $doc->findnodes('/ld:data-definition/ld:global-type');
|
||||||
|
|
||||||
|
# render enums first, this allows later code to refer to them directly
|
||||||
|
my @nonenums;
|
||||||
|
for my $name (sort { $a cmp $b } keys %global_types)
|
||||||
|
{
|
||||||
|
my $type = $global_types{$name};
|
||||||
|
my $meta = $type->getAttribute('ld:meta');
|
||||||
|
if ($meta eq 'enum-type') {
|
||||||
|
render_global_enum($name, $type);
|
||||||
|
} else {
|
||||||
|
push @nonenums, $name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# render other structs/bitfields/classes
|
||||||
|
for my $name (@nonenums)
|
||||||
|
{
|
||||||
|
my $type = $global_types{$name};
|
||||||
|
my $meta = $type->getAttribute('ld:meta');
|
||||||
|
if ($meta eq 'struct-type' or $meta eq 'class-type') {
|
||||||
|
render_global_class($name, $type);
|
||||||
|
} elsif ($meta eq 'bitfield-type') {
|
||||||
|
render_global_bitfield($name, $type);
|
||||||
|
} else {
|
||||||
|
print "no render global type $meta\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# render globals
|
||||||
|
render_global_objects($doc->findnodes('/ld:data-definition/ld:global-object'));
|
||||||
|
|
||||||
|
|
||||||
|
open FH, ">$output";
|
||||||
|
print FH "module DFHack\n";
|
||||||
|
print FH "$_\n" for @lines_rb;
|
||||||
|
print FH "end\n";
|
||||||
|
close FH;
|
@ -0,0 +1,266 @@
|
|||||||
|
module DFHack
|
||||||
|
|
||||||
|
# allocate a new building object
|
||||||
|
def self.building_alloc(type, subtype=-1, custom=-1)
|
||||||
|
cls = rtti_n2c[BuildingType::Classname[type].to_sym]
|
||||||
|
raise "invalid building type #{type.inspect}" if not cls
|
||||||
|
bld = cls.cpp_new
|
||||||
|
bld.race = ui.race_id
|
||||||
|
bld.setSubtype(subtype) if subtype != -1
|
||||||
|
bld.setCustomType(custom) if custom != -1
|
||||||
|
case type
|
||||||
|
when :Furnace; bld.melt_remainder[world.raws.inorganics.length] = 0
|
||||||
|
when :Coffin; bld.initBurialFlags
|
||||||
|
when :Trap; bld.unk_cc = 500 if bld.trap_type == :PressurePlate
|
||||||
|
end
|
||||||
|
bld
|
||||||
|
end
|
||||||
|
|
||||||
|
# used by building_setsize
|
||||||
|
def self.building_check_bridge_support(bld)
|
||||||
|
x1 = bld.x1-1
|
||||||
|
x2 = bld.x2+1
|
||||||
|
y1 = bld.y1-1
|
||||||
|
y2 = bld.y2+1
|
||||||
|
z = bld.z
|
||||||
|
(x1..x2).each { |x|
|
||||||
|
(y1..y2).each { |y|
|
||||||
|
next if ((x == x1 or x == x2) and
|
||||||
|
(y == y1 or y == y2))
|
||||||
|
if mb = map_block_at(x, y, z) and tile = mb.tiletype[x%16][y%16] and TiletypeShape::BasicShape[Tiletype::Shape[tile]] == :Open
|
||||||
|
bld.gate_flags.has_support = true
|
||||||
|
return
|
||||||
|
end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bld.gate_flags.has_support = false
|
||||||
|
end
|
||||||
|
|
||||||
|
# sets x2/centerx/y2/centery from x1/y1/bldtype
|
||||||
|
# x2/y2 preserved for :FarmPlot etc
|
||||||
|
def self.building_setsize(bld)
|
||||||
|
bld.x2 = bld.x1 if bld.x1 > bld.x2
|
||||||
|
bld.y2 = bld.y1 if bld.y1 > bld.y2
|
||||||
|
case bld.getType
|
||||||
|
when :Bridge
|
||||||
|
bld.centerx = bld.x1 + (bld.x2+1-bld.x1)/2
|
||||||
|
bld.centery = bld.y1 + (bld.y2+1-bld.y1)/2
|
||||||
|
building_check_bridge_support(bld)
|
||||||
|
when :FarmPlot, :RoadDirt, :RoadPaved, :Stockpile, :Civzone
|
||||||
|
bld.centerx = bld.x1 + (bld.x2+1-bld.x1)/2
|
||||||
|
bld.centery = bld.y1 + (bld.y2+1-bld.y1)/2
|
||||||
|
when :TradeDepot, :Shop
|
||||||
|
bld.x2 = bld.x1+4
|
||||||
|
bld.y2 = bld.y1+4
|
||||||
|
bld.centerx = bld.x1+2
|
||||||
|
bld.centery = bld.y1+2
|
||||||
|
when :SiegeEngine, :Windmill, :Wagon
|
||||||
|
bld.x2 = bld.x1+2
|
||||||
|
bld.y2 = bld.y1+2
|
||||||
|
bld.centerx = bld.x1+1
|
||||||
|
bld.centery = bld.y1+1
|
||||||
|
when :AxleHorizontal
|
||||||
|
if bld.is_vertical == 1
|
||||||
|
bld.x2 = bld.centerx = bld.x1
|
||||||
|
bld.centery = bld.y1 + (bld.y2+1-bld.y1)/2
|
||||||
|
else
|
||||||
|
bld.centerx = bld.x1 + (bld.x2+1-bld.x1)/2
|
||||||
|
bld.y2 = bld.centery = bld.y1
|
||||||
|
end
|
||||||
|
when :WaterWheel
|
||||||
|
if bld.is_vertical == 1
|
||||||
|
bld.x2 = bld.centerx = bld.x1
|
||||||
|
bld.y2 = bld.y1+2
|
||||||
|
bld.centery = bld.y1+1
|
||||||
|
else
|
||||||
|
bld.x2 = bld.x1+2
|
||||||
|
bld.centerx = bld.x1+1
|
||||||
|
bld.y2 = bld.centery = bld.y1
|
||||||
|
end
|
||||||
|
when :Workshop, :Furnace
|
||||||
|
# Furnace = Custom or default case only
|
||||||
|
case bld.type
|
||||||
|
when :Quern, :Millstone, :Tool
|
||||||
|
bld.x2 = bld.centerx = bld.x1
|
||||||
|
bld.y2 = bld.centery = bld.y1
|
||||||
|
when :Siege, :Kennels
|
||||||
|
bld.x2 = bld.x1+4
|
||||||
|
bld.y2 = bld.y1+4
|
||||||
|
bld.centerx = bld.x1+2
|
||||||
|
bld.centery = bld.y1+2
|
||||||
|
when :Custom
|
||||||
|
if bdef = world.raws.buildings.all.binsearch(bld.getCustomType)
|
||||||
|
bld.x2 = bld.x1 + bdef.dim_x - 1
|
||||||
|
bld.y2 = bld.y1 + bdef.dim_y - 1
|
||||||
|
bld.centerx = bld.x1 + bdef.workloc_x
|
||||||
|
bld.centery = bld.y1 + bdef.workloc_y
|
||||||
|
end
|
||||||
|
else
|
||||||
|
bld.x2 = bld.x1+2
|
||||||
|
bld.y2 = bld.y1+2
|
||||||
|
bld.centerx = bld.x1+1
|
||||||
|
bld.centery = bld.y1+1
|
||||||
|
end
|
||||||
|
when :ScrewPump
|
||||||
|
case bld.direction
|
||||||
|
when :FromEast
|
||||||
|
bld.x2 = bld.centerx = bld.x1+1
|
||||||
|
bld.y2 = bld.centery = bld.y1
|
||||||
|
when :FromSouth
|
||||||
|
bld.x2 = bld.centerx = bld.x1
|
||||||
|
bld.y2 = bld.centery = bld.y1+1
|
||||||
|
when :FromWest
|
||||||
|
bld.x2 = bld.x1+1
|
||||||
|
bld.y2 = bld.centery = bld.y1
|
||||||
|
bld.centerx = bld.x1
|
||||||
|
else
|
||||||
|
bld.x2 = bld.x1+1
|
||||||
|
bld.y2 = bld.centery = bld.y1
|
||||||
|
bld.centerx = bld.x1
|
||||||
|
end
|
||||||
|
when :Well
|
||||||
|
bld.bucket_z = bld.z
|
||||||
|
bld.x2 = bld.centerx = bld.x1
|
||||||
|
bld.y2 = bld.centery = bld.y1
|
||||||
|
when :Construction
|
||||||
|
bld.x2 = bld.centerx = bld.x1
|
||||||
|
bld.y2 = bld.centery = bld.y1
|
||||||
|
bld.setMaterialAmount(1)
|
||||||
|
return
|
||||||
|
else
|
||||||
|
bld.x2 = bld.centerx = bld.x1
|
||||||
|
bld.y2 = bld.centery = bld.y1
|
||||||
|
end
|
||||||
|
bld.setMaterialAmount((bld.x2-bld.x1+1)*(bld.y2-bld.y1+1)/4+1)
|
||||||
|
end
|
||||||
|
|
||||||
|
# set building at position, with optional width/height
|
||||||
|
def self.building_position(bld, pos, w=nil, h=nil)
|
||||||
|
bld.x1 = pos.x
|
||||||
|
bld.y1 = pos.y
|
||||||
|
bld.z = pos.z
|
||||||
|
bld.x2 = bld.x1+w-1 if w
|
||||||
|
bld.y2 = bld.y1+h-1 if h
|
||||||
|
building_setsize(bld)
|
||||||
|
end
|
||||||
|
|
||||||
|
# set map occupancy/stockpile/etc for a building
|
||||||
|
def self.building_setoccupancy(bld)
|
||||||
|
stockpile = (bld.getType == :Stockpile)
|
||||||
|
complete = (bld.getBuildStage >= bld.getMaxBuildStage)
|
||||||
|
extents = (bld.room.extents and bld.isExtentShaped)
|
||||||
|
|
||||||
|
z = bld.z
|
||||||
|
(bld.x1..bld.x2).each { |x|
|
||||||
|
(bld.y1..bld.y2).each { |y|
|
||||||
|
next if !extents or bld.room.extents[bld.room.width*(y-bld.room.y)+(x-bld.room.x)] == 0
|
||||||
|
next if not mb = map_block_at(x, y, z)
|
||||||
|
des = mb.designation[x%16][y%16]
|
||||||
|
des.pile = stockpile
|
||||||
|
des.dig = :No
|
||||||
|
if complete
|
||||||
|
bld.updateOccupancy(x, y)
|
||||||
|
else
|
||||||
|
mb.occupancy[x%16][y%16].building = :Planned
|
||||||
|
end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
# link bld into other rooms if it is inside their extents
|
||||||
|
def self.building_linkrooms(bld)
|
||||||
|
didstuff = false
|
||||||
|
world.buildings.other[:ANY_FREE].each { |ob|
|
||||||
|
next if !ob.is_room or ob.z != bld.z
|
||||||
|
next if !ob.room.extents or !ob.isExtentShaped or ob.room.extents[ob.room.width*(bld.y1-ob.room.y)+(bld.x1-ob.room.x)] == 0
|
||||||
|
didstuff = true
|
||||||
|
ob.children << bld
|
||||||
|
bld.parents << ob
|
||||||
|
}
|
||||||
|
ui.equipment.update.buildings = true if didstuff
|
||||||
|
end
|
||||||
|
|
||||||
|
# link the building into the world, set map data, link rooms, bld.id
|
||||||
|
def self.building_link(bld)
|
||||||
|
bld.id = df.building_next_id
|
||||||
|
df.building_next_id += 1
|
||||||
|
|
||||||
|
world.buildings.all << bld
|
||||||
|
bld.categorize(true)
|
||||||
|
building_setoccupancy(bld) if bld.isSettingOccupancy
|
||||||
|
building_linkrooms(bld)
|
||||||
|
end
|
||||||
|
|
||||||
|
# set a design for the building
|
||||||
|
def self.building_createdesign(bld, rough=true)
|
||||||
|
job = bld.jobs[0]
|
||||||
|
job.mat_type = bld.mat_type
|
||||||
|
job.mat_index = bld.mat_index
|
||||||
|
if bld.needsDesign
|
||||||
|
bld.design = BuildingDesign.cpp_new
|
||||||
|
bld.design.flags.rough = rough
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# creates a job to build bld, return it
|
||||||
|
def self.building_linkforconstruct(bld)
|
||||||
|
building_link bld
|
||||||
|
ref = GeneralRefBuildingHolderst.cpp_new
|
||||||
|
ref.building_id = bld.id
|
||||||
|
job = Job.cpp_new
|
||||||
|
job.job_type = :ConstructBuilding
|
||||||
|
job.pos = [bld.centerx, bld.centery, bld.z]
|
||||||
|
job.references << ref
|
||||||
|
bld.jobs << job
|
||||||
|
job_link job
|
||||||
|
job
|
||||||
|
end
|
||||||
|
|
||||||
|
# construct a building with items or JobItems
|
||||||
|
def self.building_construct(bld, items)
|
||||||
|
job = building_linkforconstruct(bld)
|
||||||
|
rough = false
|
||||||
|
items.each { |item|
|
||||||
|
if items.kind_of?(JobItem)
|
||||||
|
item.quantity = (bld.x2-bld.x1+1)*(bld.y2-bld.y1+1)/4+1 if item.quantity < 0
|
||||||
|
job.job_items << item
|
||||||
|
else
|
||||||
|
job_attachitem(job, item, :Hauled)
|
||||||
|
end
|
||||||
|
rough = true if item.getType == :BOULDER
|
||||||
|
bld.mat_type = item.getMaterial if bld.mat_type == -1
|
||||||
|
bld.mat_index = item.getMaterialIndex if bld.mat_index == -1
|
||||||
|
}
|
||||||
|
building_createdesign(bld, rough)
|
||||||
|
end
|
||||||
|
|
||||||
|
# creates a job to deconstruct the building
|
||||||
|
def self.building_deconstruct(bld)
|
||||||
|
job = Job.cpp_new
|
||||||
|
refbuildingholder = GeneralRefBuildingHolderst.cpp_new
|
||||||
|
job.job_type = :DestroyBuilding
|
||||||
|
refbuildingholder.building_id = building.id
|
||||||
|
job.references << refbuildingholder
|
||||||
|
building.jobs << job
|
||||||
|
job_link job
|
||||||
|
job
|
||||||
|
end
|
||||||
|
|
||||||
|
# exemple usage
|
||||||
|
def self.buildbed(pos=cursor)
|
||||||
|
suspend {
|
||||||
|
raise 'where to ?' if pos.x < 0
|
||||||
|
|
||||||
|
item = world.items.all.find { |i|
|
||||||
|
i.kind_of?(ItemBedst) and
|
||||||
|
i.itemrefs.empty? and
|
||||||
|
!i.flags.in_job
|
||||||
|
}
|
||||||
|
raise 'no free bed, build more !' if not item
|
||||||
|
|
||||||
|
bld = building_alloc(:Bed)
|
||||||
|
building_position(bld, pos)
|
||||||
|
building_construct(bld, [item])
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,152 @@
|
|||||||
|
module DFHack
|
||||||
|
def self.each_tree(material=:any)
|
||||||
|
@raws_tree_name ||= {}
|
||||||
|
if @raws_tree_name.empty?
|
||||||
|
df.world.raws.plants.all.each_with_index { |p, idx|
|
||||||
|
@raws_tree_name[idx] = p.id if p.flags[:TREE]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
if material != :any
|
||||||
|
mat = match_rawname(material, @raws_tree_name.values)
|
||||||
|
unless wantmat = @raws_tree_name.index(mat)
|
||||||
|
raise "invalid tree material #{material}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
world.plants.all.each { |plant|
|
||||||
|
next if not @raws_tree_name[plant.material]
|
||||||
|
next if wantmat and plant.material != wantmat
|
||||||
|
yield plant
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.each_shrub(material=:any)
|
||||||
|
@raws_shrub_name ||= {}
|
||||||
|
if @raws_tree_name.empty?
|
||||||
|
df.world.raws.plants.all.each_with_index { |p, idx|
|
||||||
|
@raws_shrub_name[idx] = p.id if not p.flags[:GRASS] and not p.flags[:TREE]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
if material != :any
|
||||||
|
mat = match_rawname(material, @raws_shrub_name.values)
|
||||||
|
unless wantmat = @raws_shrub_name.index(mat)
|
||||||
|
raise "invalid shrub material #{material}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
SaplingToTreeAge = 120960
|
||||||
|
def self.cuttrees(material=nil, count_max=100)
|
||||||
|
if !material
|
||||||
|
# list trees
|
||||||
|
cnt = Hash.new(0)
|
||||||
|
suspend {
|
||||||
|
each_tree { |plant|
|
||||||
|
next if plant.grow_counter < SaplingToTreeAge
|
||||||
|
next if map_designation_at(plant).hidden
|
||||||
|
cnt[plant.material] += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cnt.sort_by { |mat, c| c }.each { |mat, c|
|
||||||
|
name = @raws_tree_name[mat]
|
||||||
|
puts " #{name} #{c}"
|
||||||
|
}
|
||||||
|
else
|
||||||
|
cnt = 0
|
||||||
|
suspend {
|
||||||
|
each_tree(material) { |plant|
|
||||||
|
next if plant.grow_counter < SaplingToTreeAge
|
||||||
|
b = map_block_at(plant)
|
||||||
|
d = b.designation[plant.pos.x%16][plant.pos.y%16]
|
||||||
|
next if d.hidden
|
||||||
|
if d.dig == :No
|
||||||
|
d.dig = :Default
|
||||||
|
b.flags.designated = true
|
||||||
|
cnt += 1
|
||||||
|
break if cnt == count_max
|
||||||
|
end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
puts "Updated #{cnt} plant designations"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.growtrees(material=nil, count_max=100)
|
||||||
|
if !material
|
||||||
|
# list plants
|
||||||
|
cnt = Hash.new(0)
|
||||||
|
suspend {
|
||||||
|
each_tree { |plant|
|
||||||
|
next if plant.grow_counter >= SaplingToTreeAge
|
||||||
|
next if map_designation_at(plant).hidden
|
||||||
|
cnt[plant.material] += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cnt.sort_by { |mat, c| c }.each { |mat, c|
|
||||||
|
name = @raws_tree_name[mat]
|
||||||
|
puts " #{name} #{c}"
|
||||||
|
}
|
||||||
|
else
|
||||||
|
cnt = 0
|
||||||
|
suspend {
|
||||||
|
each_tree(material) { |plant|
|
||||||
|
next if plant.grow_counter >= SaplingToTreeAge
|
||||||
|
next if map_designation_at(plant).hidden
|
||||||
|
plant.grow_counter = SaplingToTreeAge
|
||||||
|
cnt += 1
|
||||||
|
break if cnt == count_max
|
||||||
|
}
|
||||||
|
}
|
||||||
|
puts "Grown #{cnt} saplings"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.growcrops(material=nil, count_max=100)
|
||||||
|
@raws_plant_name ||= {}
|
||||||
|
@raws_plant_growdur ||= {}
|
||||||
|
if @raws_plant_name.empty?
|
||||||
|
df.world.raws.plants.all.each_with_index { |p, idx|
|
||||||
|
@raws_plant_name[idx] = p.id
|
||||||
|
@raws_plant_growdur[idx] = p.growdur
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
if !material
|
||||||
|
cnt = Hash.new(0)
|
||||||
|
suspend {
|
||||||
|
world.items.other[:SEEDS].each { |seed|
|
||||||
|
next if not seed.flags.in_building
|
||||||
|
next if not seed.itemrefs.find { |ref| ref._rtti_classname == :general_ref_building_holderst }
|
||||||
|
next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index]
|
||||||
|
cnt[seed.mat_index] += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cnt.sort_by { |mat, c| c }.each { |mat, c|
|
||||||
|
name = world.raws.plants.all[mat].id
|
||||||
|
puts " #{name} #{c}"
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if material != :any
|
||||||
|
mat = match_rawname(material, @raws_plant_name.values)
|
||||||
|
unless wantmat = @raws_plant_name.index(mat)
|
||||||
|
raise "invalid plant material #{material}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
cnt = 0
|
||||||
|
suspend {
|
||||||
|
world.items.other[:SEEDS].each { |seed|
|
||||||
|
next if wantmat and seed.mat_index != wantmat
|
||||||
|
next if not seed.flags.in_building
|
||||||
|
next if not seed.itemrefs.find { |ref| ref._rtti_classname == :general_ref_building_holderst }
|
||||||
|
next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index]
|
||||||
|
seed.grow_counter = @raws_plant_growdur[seed.mat_index]
|
||||||
|
cnt += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
puts "Grown #{cnt} crops"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,52 @@
|
|||||||
|
module DFHack
|
||||||
|
# returns an Array of all units that are current fort citizen (dwarves, on map, not hostile)
|
||||||
|
def self.unit_citizens
|
||||||
|
race = ui.race_id
|
||||||
|
civ = ui.civ_id
|
||||||
|
world.units.active.find_all { |u|
|
||||||
|
u.race == race and u.civ_id == civ and !u.flags1.dead and !u.flags1.merchant and
|
||||||
|
!u.flags1.diplomat and !u.flags2.resident and !u.flags3.ghostly and
|
||||||
|
!u.curse.add_tags1.OPPOSED_TO_LIFE and !u.curse.add_tags1.CRAZED and
|
||||||
|
u.mood != :Berserk
|
||||||
|
# TODO check curse ; currently this should keep vampires, but may include werebeasts
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
# list workers (citizen, not crazy / child / inmood / noble)
|
||||||
|
def self.unit_workers
|
||||||
|
unit_citizens.find_all { |u|
|
||||||
|
u.mood == :None and
|
||||||
|
u.profession != :CHILD and
|
||||||
|
u.profession != :BABY and
|
||||||
|
# TODO MENIAL_WORK_EXEMPTION_SPOUSE
|
||||||
|
!unit_entitypositions(u).find { |pos| pos.flags[:MENIAL_WORK_EXEMPTION] }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
# list currently idle workers
|
||||||
|
def self.unit_idlers
|
||||||
|
unit_workers.find_all { |u|
|
||||||
|
# current_job includes eat/drink/sleep/pickupequip
|
||||||
|
!u.job.current_job._getv and
|
||||||
|
# filter 'attend meeting'
|
||||||
|
u.meetings.length == 0 and
|
||||||
|
# filter soldiers (TODO check schedule)
|
||||||
|
u.military.squad_index == -1 and
|
||||||
|
# filter 'on break'
|
||||||
|
!u.status.misc_traits.find { |t| id == :OnBreak }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.unit_entitypositions(unit)
|
||||||
|
list = []
|
||||||
|
return list if not hf = world.history.figures.binsearch(unit.hist_figure_id)
|
||||||
|
hf.entity_links.each { |el|
|
||||||
|
next if el._rtti_classname != :histfig_entity_link_positionst
|
||||||
|
next if not ent = world.entities.all.binsearch(el.entity_id)
|
||||||
|
next if not pa = ent.positions.assignments.binsearch(el.assignment_id)
|
||||||
|
next if not pos = ent.positions.own.binsearch(pa.position_id)
|
||||||
|
list << pos
|
||||||
|
}
|
||||||
|
list
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,842 @@
|
|||||||
|
// blindly copied imports from fastdwarf
|
||||||
|
#include "Core.h"
|
||||||
|
#include "Console.h"
|
||||||
|
#include "Export.h"
|
||||||
|
#include "PluginManager.h"
|
||||||
|
#include "VersionInfo.h"
|
||||||
|
|
||||||
|
#include "DataDefs.h"
|
||||||
|
#include "df/world.h"
|
||||||
|
#include "df/unit.h"
|
||||||
|
|
||||||
|
#include "tinythread.h"
|
||||||
|
|
||||||
|
using namespace DFHack;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// DFHack stuff
|
||||||
|
|
||||||
|
|
||||||
|
static int df_loadruby(void);
|
||||||
|
static void df_unloadruby(void);
|
||||||
|
static void df_rubythread(void*);
|
||||||
|
static command_result df_rubyload(color_ostream &out, std::vector <std::string> & parameters);
|
||||||
|
static command_result df_rubyeval(color_ostream &out, std::vector <std::string> & parameters);
|
||||||
|
static void ruby_bind_dfhack(void);
|
||||||
|
|
||||||
|
// inter-thread communication stuff
|
||||||
|
enum RB_command {
|
||||||
|
RB_IDLE,
|
||||||
|
RB_INIT,
|
||||||
|
RB_DIE,
|
||||||
|
RB_EVAL,
|
||||||
|
RB_CUSTOM,
|
||||||
|
};
|
||||||
|
tthread::mutex *m_irun;
|
||||||
|
tthread::mutex *m_mutex;
|
||||||
|
static RB_command r_type;
|
||||||
|
static const char *r_command;
|
||||||
|
static command_result r_result;
|
||||||
|
static tthread::thread *r_thread;
|
||||||
|
static int onupdate_active;
|
||||||
|
|
||||||
|
DFHACK_PLUGIN("ruby")
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||||
|
{
|
||||||
|
// fail silently instead of spamming the console with 'failed to initialize' if libruby is not present
|
||||||
|
// the error is still logged in stderr.log
|
||||||
|
if (!df_loadruby())
|
||||||
|
return CR_OK;
|
||||||
|
|
||||||
|
m_irun = new tthread::mutex();
|
||||||
|
m_mutex = new tthread::mutex();
|
||||||
|
r_type = RB_INIT;
|
||||||
|
|
||||||
|
r_thread = new tthread::thread(df_rubythread, 0);
|
||||||
|
|
||||||
|
while (r_type != RB_IDLE)
|
||||||
|
tthread::this_thread::yield();
|
||||||
|
|
||||||
|
m_irun->lock();
|
||||||
|
|
||||||
|
if (r_result == CR_FAILURE)
|
||||||
|
return CR_FAILURE;
|
||||||
|
|
||||||
|
onupdate_active = 0;
|
||||||
|
|
||||||
|
commands.push_back(PluginCommand("rb_load",
|
||||||
|
"Ruby interpreter. Loads the given ruby script.",
|
||||||
|
df_rubyload));
|
||||||
|
|
||||||
|
commands.push_back(PluginCommand("rb_eval",
|
||||||
|
"Ruby interpreter. Eval() a ruby string.",
|
||||||
|
df_rubyeval));
|
||||||
|
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||||
|
{
|
||||||
|
if (!r_thread)
|
||||||
|
return CR_OK;
|
||||||
|
|
||||||
|
m_mutex->lock();
|
||||||
|
|
||||||
|
r_type = RB_DIE;
|
||||||
|
r_command = 0;
|
||||||
|
m_irun->unlock();
|
||||||
|
|
||||||
|
r_thread->join();
|
||||||
|
|
||||||
|
delete r_thread;
|
||||||
|
r_thread = 0;
|
||||||
|
delete m_irun;
|
||||||
|
m_mutex->unlock();
|
||||||
|
delete m_mutex;
|
||||||
|
|
||||||
|
df_unloadruby();
|
||||||
|
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// send a single ruby line to be evaluated by the ruby thread
|
||||||
|
static command_result plugin_eval_rb(const char *command)
|
||||||
|
{
|
||||||
|
command_result ret;
|
||||||
|
|
||||||
|
// serialize 'accesses' to the ruby thread
|
||||||
|
m_mutex->lock();
|
||||||
|
if (!r_thread)
|
||||||
|
// raced with plugin_shutdown ?
|
||||||
|
return CR_OK;
|
||||||
|
|
||||||
|
r_type = RB_EVAL;
|
||||||
|
r_command = command;
|
||||||
|
m_irun->unlock();
|
||||||
|
|
||||||
|
// could use a condition_variable or something...
|
||||||
|
while (r_type != RB_IDLE)
|
||||||
|
tthread::this_thread::yield();
|
||||||
|
|
||||||
|
// XXX non-atomic with previous r_type change check
|
||||||
|
ret = r_result;
|
||||||
|
|
||||||
|
m_irun->lock();
|
||||||
|
m_mutex->unlock();
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static command_result plugin_eval_rb(std::string &command)
|
||||||
|
{
|
||||||
|
return plugin_eval_rb(command.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
|
||||||
|
{
|
||||||
|
if (!r_thread)
|
||||||
|
return CR_OK;
|
||||||
|
|
||||||
|
if (!onupdate_active)
|
||||||
|
return CR_OK;
|
||||||
|
|
||||||
|
return plugin_eval_rb("DFHack.onupdate");
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_change_event e)
|
||||||
|
{
|
||||||
|
if (!r_thread)
|
||||||
|
return CR_OK;
|
||||||
|
|
||||||
|
std::string cmd = "DFHack.onstatechange ";
|
||||||
|
switch (e) {
|
||||||
|
#define SCASE(s) case SC_ ## s : cmd += ":" # s ; break
|
||||||
|
SCASE(WORLD_LOADED);
|
||||||
|
SCASE(WORLD_UNLOADED);
|
||||||
|
SCASE(MAP_LOADED);
|
||||||
|
SCASE(MAP_UNLOADED);
|
||||||
|
SCASE(VIEWSCREEN_CHANGED);
|
||||||
|
SCASE(CORE_INITIALIZED);
|
||||||
|
SCASE(BEGIN_UNLOAD);
|
||||||
|
#undef SCASE
|
||||||
|
}
|
||||||
|
|
||||||
|
return plugin_eval_rb(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static command_result df_rubyload(color_ostream &out, std::vector <std::string> & parameters)
|
||||||
|
{
|
||||||
|
if (parameters.size() == 1 && (parameters[0] == "help" || parameters[0] == "?"))
|
||||||
|
{
|
||||||
|
out.print("This command loads the ruby script whose path is given as parameter, and run it.\n");
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string cmd = "load '";
|
||||||
|
cmd += parameters[0]; // TODO escape singlequotes
|
||||||
|
cmd += "'";
|
||||||
|
|
||||||
|
return plugin_eval_rb(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static command_result df_rubyeval(color_ostream &out, std::vector <std::string> & parameters)
|
||||||
|
{
|
||||||
|
command_result ret;
|
||||||
|
|
||||||
|
if (parameters.size() == 1 && (parameters[0] == "help" || parameters[0] == "?"))
|
||||||
|
{
|
||||||
|
out.print("This command executes an arbitrary ruby statement.\n");
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string full = "";
|
||||||
|
|
||||||
|
for (unsigned i=0 ; i<parameters.size() ; ++i) {
|
||||||
|
full += parameters[i];
|
||||||
|
if (i != parameters.size()-1)
|
||||||
|
full += " ";
|
||||||
|
}
|
||||||
|
|
||||||
|
return plugin_eval_rb(full);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ruby stuff
|
||||||
|
|
||||||
|
// ruby-dev on windows is messy
|
||||||
|
// ruby.h on linux 64 is broken
|
||||||
|
// so we dynamically load libruby instead of linking it at compile time
|
||||||
|
// lib path can be set in dfhack.ini to use the system libruby, but by
|
||||||
|
// default we'll embed our own (downloaded by cmake)
|
||||||
|
|
||||||
|
// these ruby definitions are invalid for windows 64bit
|
||||||
|
typedef unsigned long VALUE;
|
||||||
|
typedef unsigned long ID;
|
||||||
|
|
||||||
|
#define Qfalse ((VALUE)0)
|
||||||
|
#define Qtrue ((VALUE)2)
|
||||||
|
#define Qnil ((VALUE)4)
|
||||||
|
|
||||||
|
#define INT2FIX(i) ((VALUE)((((long)i) << 1) | 1))
|
||||||
|
#define FIX2INT(i) (((long)i) >> 1)
|
||||||
|
#define RUBY_METHOD_FUNC(func) ((VALUE(*)(...))func)
|
||||||
|
|
||||||
|
VALUE *rb_eRuntimeError;
|
||||||
|
|
||||||
|
void (*ruby_sysinit)(int *, const char ***);
|
||||||
|
void (*ruby_init)(void);
|
||||||
|
void (*ruby_init_loadpath)(void);
|
||||||
|
void (*ruby_script)(const char*);
|
||||||
|
void (*ruby_finalize)(void);
|
||||||
|
ID (*rb_intern)(const char*);
|
||||||
|
VALUE (*rb_raise)(VALUE, const char*, ...);
|
||||||
|
VALUE (*rb_funcall)(VALUE, ID, int, ...);
|
||||||
|
VALUE (*rb_define_module)(const char*);
|
||||||
|
void (*rb_define_singleton_method)(VALUE, const char*, VALUE(*)(...), int);
|
||||||
|
void (*rb_define_const)(VALUE, const char*, VALUE);
|
||||||
|
void (*rb_load_protect)(VALUE, int, int*);
|
||||||
|
VALUE (*rb_gv_get)(const char*);
|
||||||
|
VALUE (*rb_str_new)(const char*, long);
|
||||||
|
VALUE (*rb_str_new2)(const char*);
|
||||||
|
char* (*rb_string_value_ptr)(VALUE*);
|
||||||
|
VALUE (*rb_eval_string_protect)(const char*, int*);
|
||||||
|
VALUE (*rb_ary_shift)(VALUE);
|
||||||
|
VALUE (*rb_float_new)(double);
|
||||||
|
double (*rb_num2dbl)(VALUE);
|
||||||
|
VALUE (*rb_int2inum)(long);
|
||||||
|
VALUE (*rb_uint2inum)(unsigned long);
|
||||||
|
unsigned long (*rb_num2ulong)(VALUE);
|
||||||
|
// end of rip(ruby.h)
|
||||||
|
|
||||||
|
DFHack::DFLibrary *libruby_handle;
|
||||||
|
|
||||||
|
// load the ruby library, initialize function pointers
|
||||||
|
static int df_loadruby(void)
|
||||||
|
{
|
||||||
|
const char *libpath =
|
||||||
|
#ifdef WIN32
|
||||||
|
"./libruby.dll";
|
||||||
|
#else
|
||||||
|
"hack/libruby.so";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
libruby_handle = OpenPlugin(libpath);
|
||||||
|
if (!libruby_handle) {
|
||||||
|
fprintf(stderr, "Cannot initialize ruby plugin: failed to load %s\n", libpath);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(rb_eRuntimeError = (VALUE*)LookupPlugin(libruby_handle, "rb_eRuntimeError")))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
// XXX does msvc support decltype ? might need a #define decltype typeof
|
||||||
|
// or just assign to *(void**)(&s) = ...
|
||||||
|
// ruby_sysinit is optional (ruby1.9 only)
|
||||||
|
ruby_sysinit = (decltype(ruby_sysinit))LookupPlugin(libruby_handle, "ruby_sysinit");
|
||||||
|
#define rbloadsym(s) if (!(s = (decltype(s))LookupPlugin(libruby_handle, #s))) return 0
|
||||||
|
rbloadsym(ruby_init);
|
||||||
|
rbloadsym(ruby_init_loadpath);
|
||||||
|
rbloadsym(ruby_script);
|
||||||
|
rbloadsym(ruby_finalize);
|
||||||
|
rbloadsym(rb_intern);
|
||||||
|
rbloadsym(rb_raise);
|
||||||
|
rbloadsym(rb_funcall);
|
||||||
|
rbloadsym(rb_define_module);
|
||||||
|
rbloadsym(rb_define_singleton_method);
|
||||||
|
rbloadsym(rb_define_const);
|
||||||
|
rbloadsym(rb_load_protect);
|
||||||
|
rbloadsym(rb_gv_get);
|
||||||
|
rbloadsym(rb_str_new);
|
||||||
|
rbloadsym(rb_str_new2);
|
||||||
|
rbloadsym(rb_string_value_ptr);
|
||||||
|
rbloadsym(rb_eval_string_protect);
|
||||||
|
rbloadsym(rb_ary_shift);
|
||||||
|
rbloadsym(rb_float_new);
|
||||||
|
rbloadsym(rb_num2dbl);
|
||||||
|
rbloadsym(rb_int2inum);
|
||||||
|
rbloadsym(rb_uint2inum);
|
||||||
|
rbloadsym(rb_num2ulong);
|
||||||
|
#undef rbloadsym
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void df_unloadruby(void)
|
||||||
|
{
|
||||||
|
if (libruby_handle) {
|
||||||
|
ClosePlugin(libruby_handle);
|
||||||
|
libruby_handle = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ruby thread code
|
||||||
|
static void dump_rb_error(void)
|
||||||
|
{
|
||||||
|
VALUE s, err;
|
||||||
|
|
||||||
|
err = rb_gv_get("$!");
|
||||||
|
|
||||||
|
s = rb_funcall(err, rb_intern("class"), 0);
|
||||||
|
s = rb_funcall(s, rb_intern("name"), 0);
|
||||||
|
Core::printerr("E: %s: ", rb_string_value_ptr(&s));
|
||||||
|
|
||||||
|
s = rb_funcall(err, rb_intern("message"), 0);
|
||||||
|
Core::printerr("%s\n", rb_string_value_ptr(&s));
|
||||||
|
|
||||||
|
err = rb_funcall(err, rb_intern("backtrace"), 0);
|
||||||
|
for (int i=0 ; i<8 ; ++i)
|
||||||
|
if ((s = rb_ary_shift(err)) != Qnil)
|
||||||
|
Core::printerr(" %s\n", rb_string_value_ptr(&s));
|
||||||
|
}
|
||||||
|
|
||||||
|
static color_ostream_proxy *console_proxy;
|
||||||
|
|
||||||
|
// ruby thread main loop
|
||||||
|
static void df_rubythread(void *p)
|
||||||
|
{
|
||||||
|
int state, running;
|
||||||
|
|
||||||
|
if (ruby_sysinit) {
|
||||||
|
// ruby1.9 specific API
|
||||||
|
static int argc;
|
||||||
|
static const char *argv[] = { "dfhack", 0 };
|
||||||
|
ruby_sysinit(&argc, (const char ***)&argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize the ruby interpreter
|
||||||
|
ruby_init();
|
||||||
|
ruby_init_loadpath();
|
||||||
|
// default value for the $0 "current script name"
|
||||||
|
ruby_script("dfhack");
|
||||||
|
|
||||||
|
// create the ruby objects to map DFHack to ruby methods
|
||||||
|
ruby_bind_dfhack();
|
||||||
|
|
||||||
|
console_proxy = new color_ostream_proxy(Core::getInstance().getConsole());
|
||||||
|
|
||||||
|
r_result = CR_OK;
|
||||||
|
r_type = RB_IDLE;
|
||||||
|
|
||||||
|
running = 1;
|
||||||
|
while (running) {
|
||||||
|
// wait for new command
|
||||||
|
m_irun->lock();
|
||||||
|
|
||||||
|
switch (r_type) {
|
||||||
|
case RB_IDLE:
|
||||||
|
case RB_INIT:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RB_DIE:
|
||||||
|
running = 0;
|
||||||
|
ruby_finalize();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RB_EVAL:
|
||||||
|
state = 0;
|
||||||
|
rb_eval_string_protect(r_command, &state);
|
||||||
|
if (state)
|
||||||
|
dump_rb_error();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RB_CUSTOM:
|
||||||
|
// TODO handle ruby custom commands
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
r_result = CR_OK;
|
||||||
|
r_type = RB_IDLE;
|
||||||
|
m_irun->unlock();
|
||||||
|
tthread::this_thread::yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#define BOOL_ISFALSE(v) ((v) == Qfalse || (v) == Qnil || (v) == INT2FIX(0))
|
||||||
|
|
||||||
|
// main DFHack ruby module
|
||||||
|
static VALUE rb_cDFHack;
|
||||||
|
|
||||||
|
|
||||||
|
// DFHack module ruby methods, binds specific dfhack methods
|
||||||
|
|
||||||
|
// enable/disable calls to DFHack.onupdate()
|
||||||
|
static VALUE rb_dfonupdateactive(VALUE self)
|
||||||
|
{
|
||||||
|
if (onupdate_active)
|
||||||
|
return Qtrue;
|
||||||
|
else
|
||||||
|
return Qfalse;
|
||||||
|
}
|
||||||
|
|
||||||
|
static VALUE rb_dfonupdateactiveset(VALUE self, VALUE val)
|
||||||
|
{
|
||||||
|
onupdate_active = (BOOL_ISFALSE(val) ? 0 : 1);
|
||||||
|
return Qtrue;
|
||||||
|
}
|
||||||
|
|
||||||
|
static VALUE rb_dfresume(VALUE self)
|
||||||
|
{
|
||||||
|
Core::getInstance().Resume();
|
||||||
|
return Qtrue;
|
||||||
|
}
|
||||||
|
|
||||||
|
static VALUE rb_dfsuspend(VALUE self)
|
||||||
|
{
|
||||||
|
Core::getInstance().Suspend();
|
||||||
|
return Qtrue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the delta to apply to dfhack xml addrs wrt actual memory addresses
|
||||||
|
// usage: real_addr = addr_from_xml + this_delta;
|
||||||
|
static VALUE rb_dfrebase_delta(void)
|
||||||
|
{
|
||||||
|
uint32_t expected_base_address;
|
||||||
|
uint32_t actual_base_address = 0;
|
||||||
|
#ifdef WIN32
|
||||||
|
expected_base_address = 0x00400000;
|
||||||
|
actual_base_address = (uint32_t)GetModuleHandle(0);
|
||||||
|
#else
|
||||||
|
expected_base_address = 0x08048000;
|
||||||
|
FILE *f = fopen("/proc/self/maps", "r");
|
||||||
|
char line[256];
|
||||||
|
while (fgets(line, sizeof(line), f)) {
|
||||||
|
if (strstr(line, "libs/Dwarf_Fortress")) {
|
||||||
|
actual_base_address = strtoul(line, 0, 16);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return rb_int2inum(actual_base_address - expected_base_address);
|
||||||
|
}
|
||||||
|
|
||||||
|
static VALUE rb_dfprint_str(VALUE self, VALUE s)
|
||||||
|
{
|
||||||
|
console_proxy->print("%s", rb_string_value_ptr(&s));
|
||||||
|
return Qnil;
|
||||||
|
}
|
||||||
|
|
||||||
|
static VALUE rb_dfprint_err(VALUE self, VALUE s)
|
||||||
|
{
|
||||||
|
Core::printerr("%s", rb_string_value_ptr(&s));
|
||||||
|
return Qnil;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO needs main dfhack support
|
||||||
|
this needs a custom DFHack::Plugin subclass to pass the cmdname to invoke(), to match the ruby callback
|
||||||
|
// register a ruby method as dfhack console command
|
||||||
|
// usage: DFHack.register("moo", "this commands prints moo on the console") { DFHack.puts "moo !" }
|
||||||
|
static VALUE rb_dfregister(VALUE self, VALUE name, VALUE descr)
|
||||||
|
{
|
||||||
|
commands.push_back(PluginCommand(rb_string_value_ptr(&name),
|
||||||
|
rb_string_value_ptr(&descr),
|
||||||
|
df_rubycustom));
|
||||||
|
|
||||||
|
return Qtrue;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
static VALUE rb_dfregister(VALUE self, VALUE name, VALUE descr)
|
||||||
|
{
|
||||||
|
rb_raise(*rb_eRuntimeError, "not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
static VALUE rb_dfget_global_address(VALUE self, VALUE name)
|
||||||
|
{
|
||||||
|
return rb_uint2inum(Core::getInstance().vinfo->getAddress(rb_string_value_ptr(&name)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static VALUE rb_dfget_vtable(VALUE self, VALUE name)
|
||||||
|
{
|
||||||
|
return rb_uint2inum((uint32_t)Core::getInstance().vinfo->getVTable(rb_string_value_ptr(&name)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// read the c++ class name from a vtable pointer, inspired from doReadClassName
|
||||||
|
// XXX virtual classes only! dark pointer arithmetic, use with caution !
|
||||||
|
static VALUE rb_dfget_rtti_classname(VALUE self, VALUE vptr)
|
||||||
|
{
|
||||||
|
char *ptr = (char*)rb_num2ulong(vptr);
|
||||||
|
#ifdef WIN32
|
||||||
|
char *rtti = *(char**)(ptr - 0x4);
|
||||||
|
char *typeinfo = *(char**)(rtti + 0xC);
|
||||||
|
// skip the .?AV, trim @@ from end
|
||||||
|
return rb_str_new(typeinfo+0xc, strlen(typeinfo+0xc)-2);
|
||||||
|
#else
|
||||||
|
char *typeinfo = *(char**)(ptr - 0x4);
|
||||||
|
char *typestring = *(char**)(typeinfo + 0x4);
|
||||||
|
while (*typestring >= '0' && *typestring <= '9')
|
||||||
|
typestring++;
|
||||||
|
return rb_str_new2(typestring);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static VALUE rb_dfget_vtable_ptr(VALUE self, VALUE objptr)
|
||||||
|
{
|
||||||
|
// actually, rb_dfmemory_read_int32
|
||||||
|
return rb_uint2inum(*(uint32_t*)rb_num2ulong(objptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// raw memory access
|
||||||
|
// used by the ruby class definitions
|
||||||
|
// XXX may cause game crash ! double-check your addresses !
|
||||||
|
|
||||||
|
static VALUE rb_dfmalloc(VALUE self, VALUE len)
|
||||||
|
{
|
||||||
|
char *ptr = (char*)malloc(FIX2INT(len));
|
||||||
|
if (!ptr)
|
||||||
|
rb_raise(*rb_eRuntimeError, "no memory");
|
||||||
|
memset(ptr, 0, FIX2INT(len));
|
||||||
|
return rb_uint2inum((long)ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static VALUE rb_dffree(VALUE self, VALUE ptr)
|
||||||
|
{
|
||||||
|
free((void*)rb_num2ulong(ptr));
|
||||||
|
return Qtrue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// memory reading (buffer)
|
||||||
|
static VALUE rb_dfmemory_read(VALUE self, VALUE addr, VALUE len)
|
||||||
|
{
|
||||||
|
return rb_str_new((char*)rb_num2ulong(addr), rb_num2ulong(len));
|
||||||
|
}
|
||||||
|
|
||||||
|
// memory reading (integers/floats)
|
||||||
|
static VALUE rb_dfmemory_read_int8(VALUE self, VALUE addr)
|
||||||
|
{
|
||||||
|
return rb_int2inum(*(char*)rb_num2ulong(addr));
|
||||||
|
}
|
||||||
|
static VALUE rb_dfmemory_read_int16(VALUE self, VALUE addr)
|
||||||
|
{
|
||||||
|
return rb_int2inum(*(short*)rb_num2ulong(addr));
|
||||||
|
}
|
||||||
|
static VALUE rb_dfmemory_read_int32(VALUE self, VALUE addr)
|
||||||
|
{
|
||||||
|
return rb_int2inum(*(int*)rb_num2ulong(addr));
|
||||||
|
}
|
||||||
|
|
||||||
|
static VALUE rb_dfmemory_read_float(VALUE self, VALUE addr)
|
||||||
|
{
|
||||||
|
return rb_float_new(*(float*)rb_num2ulong(addr));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// memory writing (buffer)
|
||||||
|
static VALUE rb_dfmemory_write(VALUE self, VALUE addr, VALUE raw)
|
||||||
|
{
|
||||||
|
// no stable api for raw.length between rb1.8/rb1.9 ...
|
||||||
|
int strlen = FIX2INT(rb_funcall(raw, rb_intern("length"), 0));
|
||||||
|
|
||||||
|
memcpy((void*)rb_num2ulong(addr), rb_string_value_ptr(&raw), strlen);
|
||||||
|
|
||||||
|
return Qtrue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// memory writing (integers/floats)
|
||||||
|
static VALUE rb_dfmemory_write_int8(VALUE self, VALUE addr, VALUE val)
|
||||||
|
{
|
||||||
|
*(char*)rb_num2ulong(addr) = rb_num2ulong(val);
|
||||||
|
return Qtrue;
|
||||||
|
}
|
||||||
|
static VALUE rb_dfmemory_write_int16(VALUE self, VALUE addr, VALUE val)
|
||||||
|
{
|
||||||
|
*(short*)rb_num2ulong(addr) = rb_num2ulong(val);
|
||||||
|
return Qtrue;
|
||||||
|
}
|
||||||
|
static VALUE rb_dfmemory_write_int32(VALUE self, VALUE addr, VALUE val)
|
||||||
|
{
|
||||||
|
*(int*)rb_num2ulong(addr) = rb_num2ulong(val);
|
||||||
|
return Qtrue;
|
||||||
|
}
|
||||||
|
|
||||||
|
static VALUE rb_dfmemory_write_float(VALUE self, VALUE addr, VALUE val)
|
||||||
|
{
|
||||||
|
*(float*)rb_num2ulong(addr) = rb_num2dbl(val);
|
||||||
|
return Qtrue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// stl::string
|
||||||
|
static VALUE rb_dfmemory_stlstring_init(VALUE self, VALUE addr)
|
||||||
|
{
|
||||||
|
// XXX THIS IS TERRIBLE
|
||||||
|
std::string *ptr = new std::string;
|
||||||
|
memcpy((void*)rb_num2ulong(addr), (void*)ptr, sizeof(*ptr));
|
||||||
|
return Qtrue;
|
||||||
|
}
|
||||||
|
static VALUE rb_dfmemory_read_stlstring(VALUE self, VALUE addr)
|
||||||
|
{
|
||||||
|
std::string *s = (std::string*)rb_num2ulong(addr);
|
||||||
|
return rb_str_new(s->c_str(), s->length());
|
||||||
|
}
|
||||||
|
static VALUE rb_dfmemory_write_stlstring(VALUE self, VALUE addr, VALUE val)
|
||||||
|
{
|
||||||
|
std::string *s = (std::string*)rb_num2ulong(addr);
|
||||||
|
int strlen = FIX2INT(rb_funcall(val, rb_intern("length"), 0));
|
||||||
|
s->assign(rb_string_value_ptr(&val), strlen);
|
||||||
|
return Qtrue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// vector access
|
||||||
|
static VALUE rb_dfmemory_vec_init(VALUE self, VALUE addr)
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> *ptr = new std::vector<uint8_t>;
|
||||||
|
memcpy((void*)rb_num2ulong(addr), (void*)ptr, sizeof(*ptr));
|
||||||
|
return Qtrue;
|
||||||
|
}
|
||||||
|
// vector<uint8>
|
||||||
|
static VALUE rb_dfmemory_vec8_length(VALUE self, VALUE addr)
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> *v = (std::vector<uint8_t>*)rb_num2ulong(addr);
|
||||||
|
return rb_uint2inum(v->size());
|
||||||
|
}
|
||||||
|
static VALUE rb_dfmemory_vec8_ptrat(VALUE self, VALUE addr, VALUE idx)
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> *v = (std::vector<uint8_t>*)rb_num2ulong(addr);
|
||||||
|
return rb_uint2inum((uint32_t)&v->at(FIX2INT(idx)));
|
||||||
|
}
|
||||||
|
static VALUE rb_dfmemory_vec8_insert(VALUE self, VALUE addr, VALUE idx, VALUE val)
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> *v = (std::vector<uint8_t>*)rb_num2ulong(addr);
|
||||||
|
v->insert(v->begin()+FIX2INT(idx), rb_num2ulong(val));
|
||||||
|
return Qtrue;
|
||||||
|
}
|
||||||
|
static VALUE rb_dfmemory_vec8_delete(VALUE self, VALUE addr, VALUE idx)
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> *v = (std::vector<uint8_t>*)rb_num2ulong(addr);
|
||||||
|
v->erase(v->begin()+FIX2INT(idx));
|
||||||
|
return Qtrue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// vector<uint16>
|
||||||
|
static VALUE rb_dfmemory_vec16_length(VALUE self, VALUE addr)
|
||||||
|
{
|
||||||
|
std::vector<uint16_t> *v = (std::vector<uint16_t>*)rb_num2ulong(addr);
|
||||||
|
return rb_uint2inum(v->size());
|
||||||
|
}
|
||||||
|
static VALUE rb_dfmemory_vec16_ptrat(VALUE self, VALUE addr, VALUE idx)
|
||||||
|
{
|
||||||
|
std::vector<uint16_t> *v = (std::vector<uint16_t>*)rb_num2ulong(addr);
|
||||||
|
return rb_uint2inum((uint32_t)&v->at(FIX2INT(idx)));
|
||||||
|
}
|
||||||
|
static VALUE rb_dfmemory_vec16_insert(VALUE self, VALUE addr, VALUE idx, VALUE val)
|
||||||
|
{
|
||||||
|
std::vector<uint16_t> *v = (std::vector<uint16_t>*)rb_num2ulong(addr);
|
||||||
|
v->insert(v->begin()+FIX2INT(idx), rb_num2ulong(val));
|
||||||
|
return Qtrue;
|
||||||
|
}
|
||||||
|
static VALUE rb_dfmemory_vec16_delete(VALUE self, VALUE addr, VALUE idx)
|
||||||
|
{
|
||||||
|
std::vector<uint16_t> *v = (std::vector<uint16_t>*)rb_num2ulong(addr);
|
||||||
|
v->erase(v->begin()+FIX2INT(idx));
|
||||||
|
return Qtrue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// vector<uint32>
|
||||||
|
static VALUE rb_dfmemory_vec32_length(VALUE self, VALUE addr)
|
||||||
|
{
|
||||||
|
std::vector<uint32_t> *v = (std::vector<uint32_t>*)rb_num2ulong(addr);
|
||||||
|
return rb_uint2inum(v->size());
|
||||||
|
}
|
||||||
|
static VALUE rb_dfmemory_vec32_ptrat(VALUE self, VALUE addr, VALUE idx)
|
||||||
|
{
|
||||||
|
std::vector<uint32_t> *v = (std::vector<uint32_t>*)rb_num2ulong(addr);
|
||||||
|
return rb_uint2inum((uint32_t)&v->at(FIX2INT(idx)));
|
||||||
|
}
|
||||||
|
static VALUE rb_dfmemory_vec32_insert(VALUE self, VALUE addr, VALUE idx, VALUE val)
|
||||||
|
{
|
||||||
|
std::vector<uint32_t> *v = (std::vector<uint32_t>*)rb_num2ulong(addr);
|
||||||
|
v->insert(v->begin()+FIX2INT(idx), rb_num2ulong(val));
|
||||||
|
return Qtrue;
|
||||||
|
}
|
||||||
|
static VALUE rb_dfmemory_vec32_delete(VALUE self, VALUE addr, VALUE idx)
|
||||||
|
{
|
||||||
|
std::vector<uint32_t> *v = (std::vector<uint32_t>*)rb_num2ulong(addr);
|
||||||
|
v->erase(v->begin()+FIX2INT(idx));
|
||||||
|
return Qtrue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// vector<bool>
|
||||||
|
static VALUE rb_dfmemory_vecbool_length(VALUE self, VALUE addr)
|
||||||
|
{
|
||||||
|
std::vector<bool> *v = (std::vector<bool>*)rb_num2ulong(addr);
|
||||||
|
return rb_uint2inum(v->size());
|
||||||
|
}
|
||||||
|
static VALUE rb_dfmemory_vecbool_at(VALUE self, VALUE addr, VALUE idx)
|
||||||
|
{
|
||||||
|
std::vector<bool> *v = (std::vector<bool>*)rb_num2ulong(addr);
|
||||||
|
return v->at(FIX2INT(idx)) ? Qtrue : Qfalse;
|
||||||
|
}
|
||||||
|
static VALUE rb_dfmemory_vecbool_setat(VALUE self, VALUE addr, VALUE idx, VALUE val)
|
||||||
|
{
|
||||||
|
std::vector<bool> *v = (std::vector<bool>*)rb_num2ulong(addr);
|
||||||
|
v->at(FIX2INT(idx)) = (BOOL_ISFALSE(val) ? 0 : 1);
|
||||||
|
return Qtrue;
|
||||||
|
}
|
||||||
|
static VALUE rb_dfmemory_vecbool_insert(VALUE self, VALUE addr, VALUE idx, VALUE val)
|
||||||
|
{
|
||||||
|
std::vector<bool> *v = (std::vector<bool>*)rb_num2ulong(addr);
|
||||||
|
v->insert(v->begin()+FIX2INT(idx), (BOOL_ISFALSE(val) ? 0 : 1));
|
||||||
|
return Qtrue;
|
||||||
|
}
|
||||||
|
static VALUE rb_dfmemory_vecbool_delete(VALUE self, VALUE addr, VALUE idx)
|
||||||
|
{
|
||||||
|
std::vector<bool> *v = (std::vector<bool>*)rb_num2ulong(addr);
|
||||||
|
v->erase(v->begin()+FIX2INT(idx));
|
||||||
|
return Qtrue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// BitArray
|
||||||
|
static VALUE rb_dfmemory_bitarray_length(VALUE self, VALUE addr)
|
||||||
|
{
|
||||||
|
DFHack::BitArray<int> *b = (DFHack::BitArray<int>*)rb_num2ulong(addr);
|
||||||
|
return rb_uint2inum(b->size*8); // b->size is in bytes
|
||||||
|
}
|
||||||
|
static VALUE rb_dfmemory_bitarray_resize(VALUE self, VALUE addr, VALUE sz)
|
||||||
|
{
|
||||||
|
DFHack::BitArray<int> *b = (DFHack::BitArray<int>*)rb_num2ulong(addr);
|
||||||
|
b->resize(rb_num2ulong(sz));
|
||||||
|
return Qtrue;
|
||||||
|
}
|
||||||
|
static VALUE rb_dfmemory_bitarray_isset(VALUE self, VALUE addr, VALUE idx)
|
||||||
|
{
|
||||||
|
DFHack::BitArray<int> *b = (DFHack::BitArray<int>*)rb_num2ulong(addr);
|
||||||
|
return b->is_set(rb_num2ulong(idx)) ? Qtrue : Qfalse;
|
||||||
|
}
|
||||||
|
static VALUE rb_dfmemory_bitarray_set(VALUE self, VALUE addr, VALUE idx, VALUE val)
|
||||||
|
{
|
||||||
|
DFHack::BitArray<int> *b = (DFHack::BitArray<int>*)rb_num2ulong(addr);
|
||||||
|
b->set(rb_num2ulong(idx), (BOOL_ISFALSE(val) ? 0 : 1));
|
||||||
|
return Qtrue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* call an arbitrary object virtual method */
|
||||||
|
static VALUE rb_dfvcall(VALUE self, VALUE cppobj, VALUE cppvoff, VALUE a0, VALUE a1, VALUE a2, VALUE a3)
|
||||||
|
{
|
||||||
|
#ifdef WIN32
|
||||||
|
__thiscall
|
||||||
|
#endif
|
||||||
|
int (*fptr)(char **me, int, int, int, int);
|
||||||
|
char **that = (char**)rb_num2ulong(cppobj);
|
||||||
|
int ret;
|
||||||
|
fptr = (decltype(fptr))*(void**)(*that + rb_num2ulong(cppvoff));
|
||||||
|
ret = fptr(that, rb_num2ulong(a0), rb_num2ulong(a1), rb_num2ulong(a2), rb_num2ulong(a3));
|
||||||
|
return rb_int2inum(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// define module DFHack and its methods
|
||||||
|
static void ruby_bind_dfhack(void) {
|
||||||
|
rb_cDFHack = rb_define_module("DFHack");
|
||||||
|
|
||||||
|
// global DFHack commands
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "onupdate_active", RUBY_METHOD_FUNC(rb_dfonupdateactive), 0);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "onupdate_active=", RUBY_METHOD_FUNC(rb_dfonupdateactiveset), 1);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "resume", RUBY_METHOD_FUNC(rb_dfresume), 0);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "do_suspend", RUBY_METHOD_FUNC(rb_dfsuspend), 0);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "get_global_address", RUBY_METHOD_FUNC(rb_dfget_global_address), 1);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "get_vtable", RUBY_METHOD_FUNC(rb_dfget_vtable), 1);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "get_rtti_classname", RUBY_METHOD_FUNC(rb_dfget_rtti_classname), 1);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "get_vtable_ptr", RUBY_METHOD_FUNC(rb_dfget_vtable_ptr), 1);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "register_dfcommand", RUBY_METHOD_FUNC(rb_dfregister), 2);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "print_str", RUBY_METHOD_FUNC(rb_dfprint_str), 1);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "print_err", RUBY_METHOD_FUNC(rb_dfprint_err), 1);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "malloc", RUBY_METHOD_FUNC(rb_dfmalloc), 1);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "free", RUBY_METHOD_FUNC(rb_dffree), 1);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "vmethod_do_call", RUBY_METHOD_FUNC(rb_dfvcall), 6);
|
||||||
|
rb_define_const(rb_cDFHack, "REBASE_DELTA", rb_dfrebase_delta());
|
||||||
|
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_read", RUBY_METHOD_FUNC(rb_dfmemory_read), 2);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_read_int8", RUBY_METHOD_FUNC(rb_dfmemory_read_int8), 1);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_read_int16", RUBY_METHOD_FUNC(rb_dfmemory_read_int16), 1);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_read_int32", RUBY_METHOD_FUNC(rb_dfmemory_read_int32), 1);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_read_float", RUBY_METHOD_FUNC(rb_dfmemory_read_float), 1);
|
||||||
|
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_write", RUBY_METHOD_FUNC(rb_dfmemory_write), 2);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_write_int8", RUBY_METHOD_FUNC(rb_dfmemory_write_int8), 2);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_write_int16", RUBY_METHOD_FUNC(rb_dfmemory_write_int16), 2);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_write_int32", RUBY_METHOD_FUNC(rb_dfmemory_write_int32), 2);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_write_float", RUBY_METHOD_FUNC(rb_dfmemory_write_float), 2);
|
||||||
|
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_stlstring_init", RUBY_METHOD_FUNC(rb_dfmemory_stlstring_init), 1);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_read_stlstring", RUBY_METHOD_FUNC(rb_dfmemory_read_stlstring), 1);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_write_stlstring", RUBY_METHOD_FUNC(rb_dfmemory_write_stlstring), 2);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_vector_init", RUBY_METHOD_FUNC(rb_dfmemory_vec_init), 1);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_vector8_length", RUBY_METHOD_FUNC(rb_dfmemory_vec8_length), 1);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_vector8_ptrat", RUBY_METHOD_FUNC(rb_dfmemory_vec8_ptrat), 2);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_vector8_insert", RUBY_METHOD_FUNC(rb_dfmemory_vec8_insert), 3);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_vector8_delete", RUBY_METHOD_FUNC(rb_dfmemory_vec8_delete), 2);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_vector16_length", RUBY_METHOD_FUNC(rb_dfmemory_vec16_length), 1);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_vector16_ptrat", RUBY_METHOD_FUNC(rb_dfmemory_vec16_ptrat), 2);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_vector16_insert", RUBY_METHOD_FUNC(rb_dfmemory_vec16_insert), 3);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_vector16_delete", RUBY_METHOD_FUNC(rb_dfmemory_vec16_delete), 2);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_vector32_length", RUBY_METHOD_FUNC(rb_dfmemory_vec32_length), 1);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_vector32_ptrat", RUBY_METHOD_FUNC(rb_dfmemory_vec32_ptrat), 2);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_vector32_insert", RUBY_METHOD_FUNC(rb_dfmemory_vec32_insert), 3);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_vector32_delete", RUBY_METHOD_FUNC(rb_dfmemory_vec32_delete), 2);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_length", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_length), 1);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_at", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_at), 2);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_setat", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_setat), 3);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_insert", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_insert), 3);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_vectorbool_delete", RUBY_METHOD_FUNC(rb_dfmemory_vecbool_delete), 2);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_bitarray_length", RUBY_METHOD_FUNC(rb_dfmemory_bitarray_length), 1);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_bitarray_resize", RUBY_METHOD_FUNC(rb_dfmemory_bitarray_resize), 2);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_bitarray_isset", RUBY_METHOD_FUNC(rb_dfmemory_bitarray_isset), 2);
|
||||||
|
rb_define_singleton_method(rb_cDFHack, "memory_bitarray_set", RUBY_METHOD_FUNC(rb_dfmemory_bitarray_set), 3);
|
||||||
|
|
||||||
|
// load the default ruby-level definitions
|
||||||
|
int state=0;
|
||||||
|
rb_load_protect(rb_str_new2("./hack/ruby.rb"), Qfalse, &state);
|
||||||
|
if (state)
|
||||||
|
dump_rb_error();
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue