820 lines
24 KiB
C++
820 lines
24 KiB
C++
/*
|
|
https://github.com/peterix/dfhack
|
|
Copyright (c) 2011 Petr Mrázek <peterix@gmail.com>
|
|
|
|
A thread-safe logging console with a line editor for windows.
|
|
|
|
Based on linenoise win32 port,
|
|
copyright 2010, Jon Griffiths <jon_p_griffiths at yahoo dot com>.
|
|
All rights reserved.
|
|
Based on linenoise, copyright 2010, Salvatore Sanfilippo <antirez at gmail dot com>.
|
|
The original linenoise can be found at: http://github.com/antirez/linenoise
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are met:
|
|
|
|
* Redistributions of source code must retain the above copyright notice,
|
|
this list of conditions and the following disclaimer.
|
|
* Redistributions in binary form must reproduce the above copyright
|
|
notice, this list of conditions and the following disclaimer in the
|
|
documentation and/or other materials provided with the distribution.
|
|
* Neither the name of Redis nor the names of its contributors may be used
|
|
to endorse or promote products derived from this software without
|
|
specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
|
|
#include <stdarg.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <istream>
|
|
#include <string>
|
|
#include <stdint.h>
|
|
|
|
#include "RemoteTools.h"
|
|
#include "PluginManager.h"
|
|
#include "MiscUtils.h"
|
|
#include "VersionInfo.h"
|
|
#include "DFHackVersion.h"
|
|
|
|
#include "modules/Materials.h"
|
|
#include "modules/Translation.h"
|
|
#include "modules/Units.h"
|
|
#include "modules/World.h"
|
|
|
|
#include "LuaTools.h"
|
|
|
|
#include "DataDefs.h"
|
|
#include "df/ui.h"
|
|
#include "df/ui_advmode.h"
|
|
#include "df/world.h"
|
|
#include "df/world_data.h"
|
|
#include "df/unit.h"
|
|
#include "df/unit_misc_trait.h"
|
|
#include "df/unit_soul.h"
|
|
#include "df/unit_skill.h"
|
|
#include "df/material.h"
|
|
#include "df/matter_state.h"
|
|
#include "df/inorganic_raw.h"
|
|
#include "df/creature_raw.h"
|
|
#include "df/plant_raw.h"
|
|
#include "df/nemesis_record.h"
|
|
#include "df/historical_figure.h"
|
|
#include "df/historical_entity.h"
|
|
#include "df/squad.h"
|
|
#include "df/squad_position.h"
|
|
#include "df/incident.h"
|
|
|
|
#include "BasicApi.pb.h"
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <sstream>
|
|
|
|
#include <memory>
|
|
|
|
using namespace DFHack;
|
|
using namespace df::enums;
|
|
using namespace dfproto;
|
|
|
|
using google::protobuf::MessageLite;
|
|
|
|
void DFHack::strVectorToRepeatedField(RepeatedPtrField<std::string> *pf,
|
|
const std::vector<std::string> &vec)
|
|
{
|
|
for (size_t i = 0; i < vec.size(); ++i)
|
|
*pf->Add() = vec[i];
|
|
}
|
|
|
|
void DFHack::describeEnum(RepeatedPtrField<EnumItemName> *pf, int base,
|
|
int size, const char* const *names)
|
|
{
|
|
for (int i = 0; i < size; i++)
|
|
{
|
|
const char *key = names[i];
|
|
if (!key)
|
|
continue;
|
|
|
|
auto item = pf->Add();
|
|
item->set_value(base+i);
|
|
item->set_name(key);
|
|
}
|
|
}
|
|
|
|
void DFHack::describeBitfield(RepeatedPtrField<EnumItemName> *pf,
|
|
int size, const bitfield_item_info *items)
|
|
{
|
|
for (int i = 0; i < size; i++)
|
|
{
|
|
const char *key = items[i].name;
|
|
if (!key && items[i].size <= 1)
|
|
continue;
|
|
|
|
auto item = pf->Add();
|
|
|
|
item->set_value(i);
|
|
if (key)
|
|
item->set_name(key);
|
|
|
|
if (items[i].size > 1)
|
|
{
|
|
item->set_bit_size(items[i].size);
|
|
i += items[i].size-1;
|
|
}
|
|
}
|
|
}
|
|
|
|
void DFHack::describeMaterial(BasicMaterialInfo *info, df::material *mat,
|
|
const BasicMaterialInfoMask *mask)
|
|
{
|
|
info->set_token(mat->id);
|
|
|
|
if (mask && mask->flags())
|
|
flagarray_to_ints(info->mutable_flags(), mat->flags);
|
|
|
|
if (!mat->prefix.empty())
|
|
info->set_name_prefix(mat->prefix);
|
|
|
|
if (!mask || mask->states_size() == 0)
|
|
{
|
|
df::matter_state state = matter_state::Solid;
|
|
int temp = (mask && mask->has_temperature()) ? mask->temperature() : 10015;
|
|
|
|
if (temp >= mat->heat.melting_point)
|
|
state = matter_state::Liquid;
|
|
if (temp >= mat->heat.boiling_point)
|
|
state = matter_state::Gas;
|
|
|
|
info->add_state_color(mat->state_color[state]);
|
|
info->add_state_name(mat->state_name[state]);
|
|
info->add_state_adj(mat->state_adj[state]);
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < mask->states_size(); i++)
|
|
{
|
|
info->add_state_color(mat->state_color[i]);
|
|
info->add_state_name(mat->state_name[i]);
|
|
info->add_state_adj(mat->state_adj[i]);
|
|
}
|
|
}
|
|
|
|
if (mask && mask->reaction())
|
|
{
|
|
for (size_t i = 0; i < mat->reaction_class.size(); i++)
|
|
info->add_reaction_class(*mat->reaction_class[i]);
|
|
|
|
for (size_t i = 0; i < mat->reaction_product.id.size(); i++)
|
|
{
|
|
auto ptr = info->add_reaction_product();
|
|
ptr->set_id(*mat->reaction_product.id[i]);
|
|
ptr->set_type(mat->reaction_product.material.mat_type[i]);
|
|
ptr->set_index(mat->reaction_product.material.mat_index[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DFHack::describeMaterial(BasicMaterialInfo *info, const MaterialInfo &mat,
|
|
const BasicMaterialInfoMask *mask)
|
|
{
|
|
assert(mat.isValid());
|
|
|
|
info->set_type(mat.type);
|
|
info->set_index(mat.index);
|
|
|
|
describeMaterial(info, mat.material, mask);
|
|
|
|
switch (mat.mode) {
|
|
case MaterialInfo::Inorganic:
|
|
info->set_token(mat.inorganic->id);
|
|
if (mask && mask->flags())
|
|
flagarray_to_ints(info->mutable_inorganic_flags(), mat.inorganic->flags);
|
|
break;
|
|
|
|
case MaterialInfo::Creature:
|
|
info->set_subtype(mat.subtype);
|
|
if (mat.figure)
|
|
{
|
|
info->set_histfig_id(mat.index);
|
|
info->set_creature_id(mat.figure->race);
|
|
}
|
|
else
|
|
info->set_creature_id(mat.index);
|
|
break;
|
|
|
|
case MaterialInfo::Plant:
|
|
info->set_plant_id(mat.index);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void DFHack::describeName(NameInfo *info, df::language_name *name)
|
|
{
|
|
if (!name->first_name.empty())
|
|
info->set_first_name(DF2UTF(name->first_name));
|
|
if (!name->nickname.empty())
|
|
info->set_nickname(DF2UTF(name->nickname));
|
|
|
|
if (name->language >= 0)
|
|
info->set_language_id(name->language);
|
|
|
|
std::string lname = Translation::TranslateName(name, false, true);
|
|
if (!lname.empty())
|
|
info->set_last_name(DF2UTF(lname));
|
|
|
|
lname = Translation::TranslateName(name, true, true);
|
|
if (!lname.empty())
|
|
info->set_english_name(DF2UTF(lname));
|
|
}
|
|
|
|
void DFHack::describeNameTriple(NameTriple *info, const std::string &name,
|
|
const std::string &plural, const std::string &adj)
|
|
{
|
|
info->set_normal(DF2UTF(name));
|
|
if (!plural.empty() && plural != name)
|
|
info->set_plural(DF2UTF(plural));
|
|
if (!adj.empty() && adj != name)
|
|
info->set_adjective(DF2UTF(adj));
|
|
}
|
|
|
|
void DFHack::describeUnit(BasicUnitInfo *info, df::unit *unit,
|
|
const BasicUnitInfoMask *mask)
|
|
{
|
|
info->set_unit_id(unit->id);
|
|
|
|
info->set_pos_x(unit->pos.x);
|
|
info->set_pos_y(unit->pos.y);
|
|
info->set_pos_z(unit->pos.z);
|
|
|
|
auto name = Units::getVisibleName(unit);
|
|
if (name->has_name)
|
|
describeName(info->mutable_name(), name);
|
|
|
|
info->set_flags1(unit->flags1.whole);
|
|
info->set_flags2(unit->flags2.whole);
|
|
info->set_flags3(unit->flags3.whole);
|
|
|
|
info->set_race(unit->race);
|
|
info->set_caste(unit->caste);
|
|
|
|
if (unit->sex >= 0)
|
|
info->set_gender(unit->sex);
|
|
if (unit->civ_id >= 0)
|
|
info->set_civ_id(unit->civ_id);
|
|
if (unit->hist_figure_id >= 0)
|
|
info->set_histfig_id(unit->hist_figure_id);
|
|
|
|
if (unit->counters.death_id >= 0)
|
|
{
|
|
info->set_death_id(unit->counters.death_id);
|
|
if (auto death = df::incident::find(unit->counters.death_id))
|
|
info->set_death_flags(death->flags.whole);
|
|
}
|
|
|
|
if (mask && mask->profession())
|
|
{
|
|
if (unit->profession >= (df::profession)0)
|
|
info->set_profession(unit->profession);
|
|
if (!unit->custom_profession.empty())
|
|
info->set_custom_profession(unit->custom_profession);
|
|
|
|
if (unit->military.squad_id >= 0)
|
|
{
|
|
info->set_squad_id(unit->military.squad_id);
|
|
info->set_squad_position(unit->military.squad_position);
|
|
}
|
|
}
|
|
|
|
if (mask && mask->labors())
|
|
{
|
|
for (size_t i = 0; i < sizeof(unit->status.labors)/sizeof(bool); i++)
|
|
if (unit->status.labors[i])
|
|
info->add_labors(i);
|
|
}
|
|
|
|
if (mask && mask->skills() && unit->status.current_soul)
|
|
{
|
|
auto &vec = unit->status.current_soul->skills;
|
|
|
|
for (size_t i = 0; i < vec.size(); i++)
|
|
{
|
|
auto skill = vec[i];
|
|
auto item = info->add_skills();
|
|
item->set_id(skill->id);
|
|
item->set_level(skill->rating);
|
|
item->set_experience(skill->experience);
|
|
}
|
|
}
|
|
|
|
if (mask && mask->misc_traits())
|
|
{
|
|
auto &vec = unit -> status.misc_traits;
|
|
|
|
for (size_t i = 0; i < vec.size(); i++)
|
|
{
|
|
auto trait = vec[i];
|
|
auto item = info->add_misc_traits();
|
|
item->set_id(trait->id);
|
|
item->set_value(trait->value);
|
|
}
|
|
}
|
|
|
|
if (unit->curse.add_tags1.whole ||
|
|
unit->curse.add_tags2.whole ||
|
|
unit->curse.rem_tags1.whole ||
|
|
unit->curse.rem_tags2.whole ||
|
|
unit->curse.name_visible)
|
|
{
|
|
auto curse = info->mutable_curse();
|
|
|
|
curse->set_add_tags1(unit->curse.add_tags1.whole);
|
|
curse->set_rem_tags1(unit->curse.rem_tags1.whole);
|
|
curse->set_add_tags2(unit->curse.add_tags2.whole);
|
|
curse->set_rem_tags2(unit->curse.rem_tags2.whole);
|
|
|
|
if (unit->curse.name_visible)
|
|
describeNameTriple(curse->mutable_name(), unit->curse.name,
|
|
unit->curse.name_plural, unit->curse.name_adjective);
|
|
}
|
|
|
|
for (size_t i = 0; i < unit->burrows.size(); i++)
|
|
info->add_burrows(unit->burrows[i]);
|
|
}
|
|
|
|
static command_result GetVersion(color_ostream &stream,
|
|
const EmptyMessage *, StringMessage *out)
|
|
{
|
|
out->set_value(Version::dfhack_version());
|
|
return CR_OK;
|
|
}
|
|
|
|
static command_result GetDFVersion(color_ostream &stream,
|
|
const EmptyMessage *, StringMessage *out)
|
|
{
|
|
out->set_value(Core::getInstance().vinfo->getVersion());
|
|
return CR_OK;
|
|
}
|
|
|
|
static command_result GetWorldInfo(color_ostream &stream,
|
|
const EmptyMessage *, GetWorldInfoOut *out)
|
|
{
|
|
using df::global::ui;
|
|
using df::global::ui_advmode;
|
|
using df::global::world;
|
|
|
|
if (!ui || !world || !Core::getInstance().isWorldLoaded())
|
|
return CR_NOT_FOUND;
|
|
|
|
df::game_type gt = game_type::DWARF_MAIN;
|
|
if (df::global::gametype)
|
|
gt = *df::global::gametype;
|
|
|
|
out->set_save_dir(world->cur_savegame.save_dir);
|
|
|
|
if (world->world_data->name.has_name)
|
|
describeName(out->mutable_world_name(), &world->world_data->name);
|
|
|
|
switch (gt)
|
|
{
|
|
case game_type::DWARF_MAIN:
|
|
case game_type::DWARF_RECLAIM:
|
|
out->set_mode(GetWorldInfoOut::MODE_DWARF);
|
|
out->set_civ_id(ui->civ_id);
|
|
out->set_site_id(ui->site_id);
|
|
out->set_group_id(ui->group_id);
|
|
out->set_race_id(ui->race_id);
|
|
break;
|
|
|
|
case game_type::ADVENTURE_MAIN:
|
|
out->set_mode(GetWorldInfoOut::MODE_ADVENTURE);
|
|
|
|
if (auto unit = vector_get(world->units.active, 0))
|
|
out->set_player_unit_id(unit->id);
|
|
|
|
if (!ui_advmode)
|
|
break;
|
|
|
|
if (auto nemesis = vector_get(world->nemesis.all, ui_advmode->player_id))
|
|
{
|
|
if (nemesis->figure)
|
|
out->set_player_histfig_id(nemesis->figure->id);
|
|
|
|
for (size_t i = 0; i < nemesis->companions.size(); i++)
|
|
{
|
|
auto unm = df::nemesis_record::find(nemesis->companions[i]);
|
|
if (!unm || !unm->figure)
|
|
continue;
|
|
out->add_companion_histfig_ids(unm->figure->id);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case game_type::VIEW_LEGENDS:
|
|
out->set_mode(GetWorldInfoOut::MODE_LEGENDS);
|
|
break;
|
|
|
|
default:
|
|
return CR_NOT_FOUND;
|
|
}
|
|
|
|
return CR_OK;
|
|
}
|
|
|
|
static command_result ListEnums(color_ostream &stream,
|
|
const EmptyMessage *, ListEnumsOut *out)
|
|
{
|
|
#define ENUM(name) describe_enum<df::name>(out->mutable_##name());
|
|
#define BITFIELD(name) describe_bitfield<df::name>(out->mutable_##name());
|
|
|
|
ENUM(material_flags);
|
|
ENUM(inorganic_flags);
|
|
|
|
BITFIELD(unit_flags1);
|
|
BITFIELD(unit_flags2);
|
|
BITFIELD(unit_flags3);
|
|
|
|
ENUM(unit_labor);
|
|
ENUM(job_skill);
|
|
|
|
BITFIELD(cie_add_tag_mask1);
|
|
BITFIELD(cie_add_tag_mask2);
|
|
|
|
describe_bitfield<df::incident::T_flags>(out->mutable_death_info_flags());
|
|
|
|
ENUM(profession);
|
|
|
|
return CR_OK;
|
|
#undef ENUM
|
|
#undef BITFIELD
|
|
}
|
|
|
|
static command_result ListJobSkills(color_ostream &stream, const EmptyMessage *, ListJobSkillsOut *out)
|
|
{
|
|
auto pf_skill = out->mutable_skill();
|
|
FOR_ENUM_ITEMS(job_skill, skill)
|
|
{
|
|
auto item = pf_skill->Add();
|
|
|
|
item->set_id(skill);
|
|
item->set_key(ENUM_KEY_STR(job_skill, skill));
|
|
item->set_caption(ENUM_ATTR_STR(job_skill, caption, skill));
|
|
item->set_caption_noun(ENUM_ATTR_STR(job_skill, caption_noun, skill));
|
|
item->set_profession(ENUM_ATTR(job_skill, profession, skill));
|
|
item->set_labor(ENUM_ATTR(job_skill, labor, skill));
|
|
item->set_type(ENUM_KEY_STR(job_skill_class, ENUM_ATTR(job_skill, type, skill)));
|
|
}
|
|
|
|
auto pf_profession = out->mutable_profession();
|
|
FOR_ENUM_ITEMS(profession, p)
|
|
{
|
|
auto item = pf_profession->Add();
|
|
|
|
item->set_id(p);
|
|
item->set_key(ENUM_KEY_STR(profession, p));
|
|
item->set_caption(ENUM_ATTR_STR(profession, caption, p));
|
|
item->set_military(ENUM_ATTR(profession, military, p));
|
|
item->set_can_assign_labor(ENUM_ATTR(profession, can_assign_labor, p));
|
|
item->set_parent(ENUM_ATTR(profession, parent, p));
|
|
}
|
|
|
|
auto pf_labor = out->mutable_labor();
|
|
FOR_ENUM_ITEMS(unit_labor, labor)
|
|
{
|
|
auto item = pf_labor->Add();
|
|
|
|
item->set_id(labor);
|
|
item->set_key(ENUM_KEY_STR(unit_labor, labor));
|
|
item->set_caption(ENUM_ATTR_STR(unit_labor, caption, labor));
|
|
}
|
|
|
|
return CR_OK;
|
|
}
|
|
|
|
static void listMaterial(ListMaterialsOut *out, int type, int index, const BasicMaterialInfoMask *mask)
|
|
{
|
|
MaterialInfo info(type, index);
|
|
if (info.isValid())
|
|
describeMaterial(out->add_value(), info, mask);
|
|
}
|
|
|
|
static command_result ListMaterials(color_ostream &stream,
|
|
const ListMaterialsIn *in, ListMaterialsOut *out)
|
|
{
|
|
auto mask = in->has_mask() ? &in->mask() : NULL;
|
|
|
|
for (int i = 0; i < in->id_list_size(); i++)
|
|
{
|
|
auto &elt = in->id_list(i);
|
|
listMaterial(out, elt.type(), elt.index(), mask);
|
|
}
|
|
|
|
if (in->builtin())
|
|
{
|
|
for (int i = 0; i < MaterialInfo::NUM_BUILTIN; i++)
|
|
listMaterial(out, i, -1, mask);
|
|
}
|
|
|
|
if (in->inorganic())
|
|
{
|
|
auto &vec = df::inorganic_raw::get_vector();
|
|
for (size_t i = 0; i < vec.size(); i++)
|
|
listMaterial(out, 0, i, mask);
|
|
}
|
|
|
|
if (in->creatures())
|
|
{
|
|
auto &vec = df::creature_raw::get_vector();
|
|
for (size_t i = 0; i < vec.size(); i++)
|
|
{
|
|
auto praw = vec[i];
|
|
|
|
for (size_t j = 0; j < praw->material.size(); j++)
|
|
listMaterial(out, MaterialInfo::CREATURE_BASE+j, i, mask);
|
|
}
|
|
}
|
|
|
|
if (in->plants())
|
|
{
|
|
auto &vec = df::plant_raw::get_vector();
|
|
for (size_t i = 0; i < vec.size(); i++)
|
|
{
|
|
auto praw = vec[i];
|
|
|
|
for (size_t j = 0; j < praw->material.size(); j++)
|
|
listMaterial(out, MaterialInfo::PLANT_BASE+j, i, mask);
|
|
}
|
|
}
|
|
|
|
return out->value_size() ? CR_OK : CR_NOT_FOUND;
|
|
}
|
|
|
|
static command_result ListUnits(color_ostream &stream,
|
|
const ListUnitsIn *in, ListUnitsOut *out)
|
|
{
|
|
auto mask = in->has_mask() ? &in->mask() : NULL;
|
|
|
|
if (in->id_list_size() > 0)
|
|
{
|
|
for (int i = 0; i < in->id_list_size(); i++)
|
|
{
|
|
auto unit = df::unit::find(in->id_list(i));
|
|
if (unit)
|
|
describeUnit(out->add_value(), unit, mask);
|
|
}
|
|
}
|
|
|
|
if (in->scan_all())
|
|
{
|
|
auto &vec = df::unit::get_vector();
|
|
|
|
for (size_t i = 0; i < vec.size(); i++)
|
|
{
|
|
auto unit = vec[i];
|
|
|
|
if (in->has_race() && unit->race != in->race())
|
|
continue;
|
|
if (in->has_civ_id() && unit->civ_id != in->civ_id())
|
|
continue;
|
|
if (in->has_dead() && Units::isDead(unit) != in->dead())
|
|
continue;
|
|
if (in->has_alive() && Units::isAlive(unit) != in->alive())
|
|
continue;
|
|
if (in->has_sane() && Units::isSane(unit) != in->sane())
|
|
continue;
|
|
|
|
describeUnit(out->add_value(), unit, mask);
|
|
}
|
|
}
|
|
|
|
return out->value_size() ? CR_OK : CR_NOT_FOUND;
|
|
}
|
|
|
|
static command_result ListSquads(color_ostream &stream,
|
|
const ListSquadsIn *in, ListSquadsOut *out)
|
|
{
|
|
auto entity = df::historical_entity::find(df::global::ui->group_id);
|
|
if (!entity)
|
|
return CR_NOT_FOUND;
|
|
|
|
for (size_t i = 0; i < entity->squads.size(); i++)
|
|
{
|
|
auto squad = df::squad::find(entity->squads[i]);
|
|
if (!squad)
|
|
continue;
|
|
|
|
auto item = out->add_value();
|
|
item->set_squad_id(squad->id);
|
|
|
|
if (squad->name.has_name)
|
|
describeName(item->mutable_name(), &squad->name);
|
|
if (!squad->alias.empty())
|
|
item->set_alias(squad->alias);
|
|
|
|
for (size_t j = 0; j < squad->positions.size(); j++)
|
|
item->add_members(squad->positions[j]->occupant);
|
|
}
|
|
|
|
return CR_OK;
|
|
}
|
|
|
|
static command_result SetUnitLabors(color_ostream &stream, const SetUnitLaborsIn *in)
|
|
{
|
|
for (int i = 0; i < in->change_size(); i++)
|
|
{
|
|
auto change = in->change(i);
|
|
auto unit = df::unit::find(change.unit_id());
|
|
|
|
if (unit)
|
|
unit->status.labors[change.labor()] = change.value();
|
|
}
|
|
|
|
return CR_OK;
|
|
}
|
|
|
|
CoreService::CoreService() {
|
|
suspend_depth = 0;
|
|
|
|
// These 2 methods must be first, so that they get id 0 and 1
|
|
addMethod("BindMethod", &CoreService::BindMethod, SF_DONT_SUSPEND | SF_ALLOW_REMOTE);
|
|
addMethod("RunCommand", &CoreService::RunCommand, SF_DONT_SUSPEND);
|
|
|
|
// Add others here:
|
|
addMethod("CoreSuspend", &CoreService::CoreSuspend, SF_DONT_SUSPEND | SF_ALLOW_REMOTE);
|
|
addMethod("CoreResume", &CoreService::CoreResume, SF_DONT_SUSPEND | SF_ALLOW_REMOTE);
|
|
|
|
addMethod("RunLua", &CoreService::RunLua);
|
|
|
|
// Functions:
|
|
addFunction("GetVersion", GetVersion, SF_DONT_SUSPEND | SF_ALLOW_REMOTE);
|
|
addFunction("GetDFVersion", GetDFVersion, SF_DONT_SUSPEND | SF_ALLOW_REMOTE);
|
|
|
|
addFunction("GetWorldInfo", GetWorldInfo, SF_ALLOW_REMOTE);
|
|
|
|
addFunction("ListEnums", ListEnums, SF_CALLED_ONCE | SF_DONT_SUSPEND | SF_ALLOW_REMOTE);
|
|
addFunction("ListJobSkills", ListJobSkills, SF_CALLED_ONCE | SF_DONT_SUSPEND | SF_ALLOW_REMOTE);
|
|
|
|
addFunction("ListMaterials", ListMaterials, SF_CALLED_ONCE | SF_ALLOW_REMOTE);
|
|
addFunction("ListUnits", ListUnits, SF_ALLOW_REMOTE);
|
|
addFunction("ListSquads", ListSquads, SF_ALLOW_REMOTE);
|
|
|
|
addFunction("SetUnitLabors", SetUnitLabors, SF_ALLOW_REMOTE);
|
|
}
|
|
|
|
CoreService::~CoreService()
|
|
{
|
|
while (suspend_depth-- > 0)
|
|
Core::getInstance().Resume();
|
|
}
|
|
|
|
command_result CoreService::BindMethod(color_ostream &stream,
|
|
const dfproto::CoreBindRequest *in,
|
|
dfproto::CoreBindReply *out)
|
|
{
|
|
ServerFunctionBase *fn = connection()->findFunction(stream, in->plugin(), in->method());
|
|
|
|
if (!fn)
|
|
{
|
|
stream.printerr("RPC method not found: %s::%s\n",
|
|
in->plugin().c_str(), in->method().c_str());
|
|
return CR_FAILURE;
|
|
}
|
|
|
|
if (fn->p_in_template->GetTypeName() != in->input_msg() ||
|
|
fn->p_out_template->GetTypeName() != in->output_msg())
|
|
{
|
|
stream.printerr("Requested wrong signature for RPC method: %s::%s\n",
|
|
in->plugin().c_str(), in->method().c_str());
|
|
return CR_FAILURE;
|
|
}
|
|
|
|
out->set_assigned_id(fn->getId());
|
|
return CR_OK;
|
|
}
|
|
|
|
command_result CoreService::RunCommand(color_ostream &stream,
|
|
const dfproto::CoreRunCommandRequest *in)
|
|
{
|
|
std::string cmd = in->command();
|
|
std::vector<std::string> args;
|
|
for (int i = 0; i < in->arguments_size(); i++)
|
|
args.push_back(in->arguments(i));
|
|
|
|
return Core::getInstance().runCommand(stream, cmd, args);
|
|
}
|
|
|
|
command_result CoreService::CoreSuspend(color_ostream &stream, const EmptyMessage*, IntMessage *cnt)
|
|
{
|
|
Core::getInstance().Suspend();
|
|
cnt->set_value(++suspend_depth);
|
|
return CR_OK;
|
|
}
|
|
|
|
command_result CoreService::CoreResume(color_ostream &stream, const EmptyMessage*, IntMessage *cnt)
|
|
{
|
|
if (suspend_depth <= 0)
|
|
return CR_WRONG_USAGE;
|
|
|
|
Core::getInstance().Resume();
|
|
cnt->set_value(--suspend_depth);
|
|
return CR_OK;
|
|
}
|
|
|
|
namespace {
|
|
struct LuaFunctionData {
|
|
command_result rv;
|
|
const dfproto::CoreRunLuaRequest *in;
|
|
StringListMessage *out;
|
|
};
|
|
}
|
|
|
|
command_result CoreService::RunLua(color_ostream &stream,
|
|
const dfproto::CoreRunLuaRequest *in,
|
|
StringListMessage *out)
|
|
{
|
|
auto L = Lua::Core::State;
|
|
LuaFunctionData data = { CR_FAILURE, in, out };
|
|
|
|
lua_pushcfunction(L, doRunLuaFunction);
|
|
lua_pushlightuserdata(L, &data);
|
|
|
|
if (!Lua::Core::SafeCall(stream, 1, 0))
|
|
return CR_FAILURE;
|
|
|
|
return data.rv;
|
|
}
|
|
|
|
int CoreService::doRunLuaFunction(lua_State *L)
|
|
{
|
|
color_ostream &out = *Lua::GetOutput(L);
|
|
auto &args = *(LuaFunctionData*)lua_touserdata(L, 1);
|
|
|
|
// Verify module name
|
|
std::string module = args.in->module();
|
|
size_t len = module.size();
|
|
|
|
bool valid = false;
|
|
|
|
if (len > 4)
|
|
{
|
|
if (module.substr(0,4) == "rpc.")
|
|
valid = true;
|
|
else if ((module[len-4] == '.' || module[len-4] == '-') && module.substr(len-3) != "rpc")
|
|
valid = true;
|
|
}
|
|
|
|
if (!valid)
|
|
{
|
|
args.rv = CR_WRONG_USAGE;
|
|
out.printerr("Only modules named rpc.* or *.rpc or *-rpc may be called.\n");
|
|
return 0;
|
|
}
|
|
|
|
// Prepare function and arguments
|
|
lua_settop(L, 0);
|
|
|
|
if (!Lua::PushModulePublic(out, L, module.c_str(), args.in->function().c_str())
|
|
|| lua_isnil(L, 1))
|
|
{
|
|
args.rv = CR_NOT_FOUND;
|
|
return 0;
|
|
}
|
|
|
|
luaL_checkstack(L, args.in->arguments_size(), "too many arguments");
|
|
|
|
for (int i = 0; i < args.in->arguments_size(); i++)
|
|
lua_pushstring(L, args.in->arguments(i).c_str());
|
|
|
|
// Call
|
|
lua_call(L, args.in->arguments_size(), LUA_MULTRET);
|
|
|
|
// Store results
|
|
int nresults = lua_gettop(L);
|
|
|
|
for (int i = 1; i <= nresults; i++)
|
|
{
|
|
size_t len;
|
|
const char *data = lua_tolstring(L, i, &len);
|
|
args.out->add_value(std::string(data, len));
|
|
}
|
|
|
|
args.rv = CR_OK;
|
|
return 0;
|
|
}
|